From ea29f7c34435635b1b9f2a3e5a12acdefce1d100 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 4 Mar 2020 17:01:37 +0100 Subject: [PATCH 001/350] Use an OsuAnimatedButton in LoginPlaceholder to get the correct animations. --- .../Online/Placeholders/LoginPlaceholder.cs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index 73b0fa27c3..a17fb8f2b1 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -4,43 +4,39 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; namespace osu.Game.Online.Placeholders { public sealed class LoginPlaceholder : Placeholder { - [Resolved(CanBeNull = true)] - private LoginOverlay login { get; set; } - public LoginPlaceholder(string actionMessage) { - AddIcon(FontAwesome.Solid.UserLock, cp => + AddArbitraryDrawable(new LoginButton(actionMessage)); + } + + private class LoginButton : OsuAnimatedButton + { + [Resolved(CanBeNull = true)] + private LoginOverlay login { get; set; } + + public LoginButton(string actionMessage) { - cp.Font = cp.Font.With(size: TEXT_SIZE); - cp.Padding = new MarginPadding { Right = 10 }; - }); + AutoSizeAxes = Axes.Both; - AddText(actionMessage); - } + Child = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) + .With(t => t.AutoSizeAxes = Axes.Both) + .With(t => t.AddIcon(FontAwesome.Solid.UserLock, icon => + { + icon.Padding = new MarginPadding { Right = 10 }; + })) + .With(t => t.AddText(actionMessage)) + .With(t => t.Margin = new MarginPadding(5)); - protected override bool OnMouseDown(MouseDownEvent e) - { - this.ScaleTo(0.8f, 4000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - this.ScaleTo(1, 1000, Easing.OutElastic); - base.OnMouseUp(e); - } - - protected override bool OnClick(ClickEvent e) - { - login?.Show(); - return base.OnClick(e); + Action = () => login?.Show(); + } } } } From b1b3e01abdc6dc7ccde647f262b1936cc6e7265b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 4 Mar 2020 22:59:48 +0100 Subject: [PATCH 002/350] Apply review suggestion. --- .../Online/Placeholders/LoginPlaceholder.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index a17fb8f2b1..543c108642 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -26,14 +26,20 @@ namespace osu.Game.Online.Placeholders { AutoSizeAxes = Axes.Both; - Child = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) - .With(t => t.AutoSizeAxes = Axes.Both) - .With(t => t.AddIcon(FontAwesome.Solid.UserLock, icon => - { - icon.Padding = new MarginPadding { Right = 10 }; - })) - .With(t => t.AddText(actionMessage)) - .With(t => t.Margin = new MarginPadding(5)); + var textFlowContainer = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding(5) + }; + + Child = textFlowContainer; + + textFlowContainer.AddIcon(FontAwesome.Solid.UserLock, icon => + { + icon.Padding = new MarginPadding { Right = 10 }; + }); + + textFlowContainer.AddText(actionMessage); Action = () => login?.Show(); } From e136ecec5f131087bd2f1f2ba4df3ee79576adde Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 6 Mar 2020 22:12:02 +0100 Subject: [PATCH 003/350] Create ClickablePlaceholder and make of use it where applicable. --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 5 +- .../RetrievalFailurePlaceholder.cs | 65 ------------------- .../Placeholders/ClickablePlaceholder.cs | 38 +++++++++++ .../Online/Placeholders/LoginPlaceholder.cs | 39 ++--------- 6 files changed, 49 insertions(+), 102 deletions(-) delete mode 100644 osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs create mode 100644 osu.Game/Online/Placeholders/ClickablePlaceholder.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 1198488bda..44c77b1bd3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.SongSelect { typeof(Placeholder), typeof(MessagePlaceholder), - typeof(RetrievalFailurePlaceholder), + typeof(ClickablePlaceholder), typeof(UserTopScoreContainer), typeof(Leaderboard), }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index a812b4dc79..fdeff7e434 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface { typeof(Placeholder), typeof(MessagePlaceholder), - typeof(RetrievalFailurePlaceholder), + typeof(ClickablePlaceholder), typeof(UserTopScoreContainer), typeof(Leaderboard), typeof(LeaderboardScore), diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e2a817aaff..cb70cfb97f 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -133,9 +134,9 @@ namespace osu.Game.Online.Leaderboards switch (placeholderState = value) { case PlaceholderState.NetworkFailure: - replacePlaceholder(new RetrievalFailurePlaceholder + replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { - OnRetry = UpdateScores, + Action = UpdateScores, }); break; diff --git a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs deleted file mode 100644 index d109f28e72..0000000000 --- a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Online.Placeholders; -using osuTK; - -namespace osu.Game.Online.Leaderboards -{ - public class RetrievalFailurePlaceholder : Placeholder - { - public Action OnRetry; - - public RetrievalFailurePlaceholder() - { - AddArbitraryDrawable(new RetryButton - { - Action = () => OnRetry?.Invoke(), - Padding = new MarginPadding { Right = 10 } - }); - - AddText(@"Couldn't retrieve scores!"); - } - - public class RetryButton : OsuHoverContainer - { - private readonly SpriteIcon icon; - - public new Action Action; - - public RetryButton() - { - AutoSizeAxes = Axes.Both; - - Child = new OsuClickableContainer - { - AutoSizeAxes = Axes.Both, - Action = () => Action?.Invoke(), - Child = icon = new SpriteIcon - { - Icon = FontAwesome.Solid.Sync, - Size = new Vector2(TEXT_SIZE), - Shadow = true, - }, - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - icon.ScaleTo(0.8f, 4000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - icon.ScaleTo(1, 1000, Easing.OutElastic); - base.OnMouseUp(e); - } - } - } -} diff --git a/osu.Game/Online/Placeholders/ClickablePlaceholder.cs b/osu.Game/Online/Placeholders/ClickablePlaceholder.cs new file mode 100644 index 0000000000..936ad79c64 --- /dev/null +++ b/osu.Game/Online/Placeholders/ClickablePlaceholder.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Online.Placeholders +{ + public class ClickablePlaceholder : Placeholder + { + public Action Action; + + public ClickablePlaceholder(string actionMessage, IconUsage icon) + { + OsuTextFlowContainer textFlow; + + AddArbitraryDrawable(new OsuAnimatedButton + { + AutoSizeAxes = Framework.Graphics.Axes.Both, + Child = textFlow = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) + { + AutoSizeAxes = Framework.Graphics.Axes.Both, + Margin = new Framework.Graphics.MarginPadding(5) + }, + Action = () => Action?.Invoke() + }); + + textFlow.AddIcon(icon, i => + { + i.Padding = new Framework.Graphics.MarginPadding { Right = 10 }; + }); + + textFlow.AddText(actionMessage); + } + } +} diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index 543c108642..f8a326a52e 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -2,47 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; namespace osu.Game.Online.Placeholders { - public sealed class LoginPlaceholder : Placeholder + public sealed class LoginPlaceholder : ClickablePlaceholder { + [Resolved(CanBeNull = true)] + private LoginOverlay login { get; set; } + public LoginPlaceholder(string actionMessage) + : base(actionMessage, FontAwesome.Solid.UserLock) { - AddArbitraryDrawable(new LoginButton(actionMessage)); - } - - private class LoginButton : OsuAnimatedButton - { - [Resolved(CanBeNull = true)] - private LoginOverlay login { get; set; } - - public LoginButton(string actionMessage) - { - AutoSizeAxes = Axes.Both; - - var textFlowContainer = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding(5) - }; - - Child = textFlowContainer; - - textFlowContainer.AddIcon(FontAwesome.Solid.UserLock, icon => - { - icon.Padding = new MarginPadding { Right = 10 }; - }); - - textFlowContainer.AddText(actionMessage); - - Action = () => login?.Show(); - } + Action = () => login?.Show(); } } } From 17cd9569ed2875c3fd418fc018b356eb2d092806 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 00:46:40 +0200 Subject: [PATCH 004/350] Introduce new storage class and manager --- .../Components/TourneyVideo.cs | 4 +-- osu.Game.Tournament/TournamentGameBase.cs | 12 ++++--- osu.Game.Tournament/TournamentStorage.cs | 34 +++++++++++++++++-- .../TournamentStorageManager.cs | 30 ++++++++++++++++ 4 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Tournament/TournamentStorageManager.cs diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 317c5f6a56..259cb95035 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -27,9 +27,9 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(TournamentStorage storage) + private void load(NewTournamentStorage storage) { - var stream = storage.GetStream($@"videos/{filename}"); + var stream = storage.VideoStorage.GetStream($@"{filename}"); if (stream != null) { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 85db9e61fb..427a33f871 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -19,6 +19,8 @@ using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API.Requests; +using osu.Framework.Logging; +using osu.Game.Tournament.Configuration; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; using osu.Game.Users; @@ -37,7 +39,7 @@ namespace osu.Game.Tournament private Storage storage; - private TournamentStorage tournamentStorage; + private NewTournamentStorage newTournamentStorage; private DependencyContainer dependencies; @@ -52,15 +54,15 @@ namespace osu.Game.Tournament } [BackgroundDependencyLoader] - private void load(Storage storage, FrameworkConfigManager frameworkConfig) + private void load(FrameworkConfigManager frameworkConfig) { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(tournamentStorage = new TournamentStorage(storage)); + dependencies.CacheAs(newTournamentStorage = new NewTournamentStorage(Host)); - Textures.AddStore(new TextureLoaderStore(tournamentStorage)); + Textures.AddStore(new TextureLoaderStore(newTournamentStorage.VideoStorage)); - this.storage = storage; + this.storage = newTournamentStorage; windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index 139ad3857b..defeceab93 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -2,18 +2,46 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.IO; +using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament { - internal class TournamentStorage : NamespacedResourceStore + internal class TournamentVideoStorage : NamespacedResourceStore { - public TournamentStorage(Storage storage) - : base(new StorageBackedResourceStore(storage), "tournament") + public TournamentVideoStorage(Storage storage) + : base(new StorageBackedResourceStore(storage), "videos") { AddExtension("m4v"); AddExtension("avi"); AddExtension("mp4"); } } + + internal class NewTournamentStorage : WrappedStorage + { + private readonly GameHost host; + private readonly TournamentStorageManager storageConfig; + public readonly TournamentVideoStorage VideoStorage; + + public NewTournamentStorage(GameHost host) + : base(host.Storage, string.Empty) + { + this.host = host; + + storageConfig = new TournamentStorageManager(host.Storage); + var customTournamentPath = storageConfig.Get(StorageConfig.CurrentTournament); + + if (!string.IsNullOrEmpty(customTournamentPath)) + { + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments/" + customTournamentPath)); + } else { + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments/default")); + } + VideoStorage = new TournamentVideoStorage(this); + Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); + } + } } diff --git a/osu.Game.Tournament/TournamentStorageManager.cs b/osu.Game.Tournament/TournamentStorageManager.cs new file mode 100644 index 0000000000..b1f84ecf44 --- /dev/null +++ b/osu.Game.Tournament/TournamentStorageManager.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Configuration; +using osu.Framework.Platform; + +namespace osu.Game.Tournament.Configuration +{ + public class TournamentStorageManager : IniConfigManager + { + protected override string Filename => "tournament.ini"; + + public TournamentStorageManager(Storage storage) + : base(storage) + { + } + + protected override void InitialiseDefaults() + { + base.InitialiseDefaults(); + Set(StorageConfig.CurrentTournament, string.Empty); + } + + } + + public enum StorageConfig + { + CurrentTournament, + } +} \ No newline at end of file From 9a20ffa8a35ac048b7279dec2fd3752061a12987 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 00:47:47 +0200 Subject: [PATCH 005/350] Rename to TournamentStorage --- osu.Game.Tournament/Components/TourneyVideo.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 8 ++++---- osu.Game.Tournament/TournamentStorage.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 259cb95035..131fa9450d 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(NewTournamentStorage storage) + private void load(TournamentStorage storage) { var stream = storage.VideoStorage.GetStream($@"{filename}"); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 427a33f871..991c586a56 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tournament private Storage storage; - private NewTournamentStorage newTournamentStorage; + private TournamentStorage tournamentStorage; private DependencyContainer dependencies; @@ -58,11 +58,11 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(newTournamentStorage = new NewTournamentStorage(Host)); + dependencies.CacheAs(tournamentStorage = new TournamentStorage(Host)); - Textures.AddStore(new TextureLoaderStore(newTournamentStorage.VideoStorage)); + Textures.AddStore(new TextureLoaderStore(tournamentStorage.VideoStorage)); - this.storage = newTournamentStorage; + this.storage = tournamentStorage; windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index defeceab93..e5d19831d0 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -20,13 +20,13 @@ namespace osu.Game.Tournament } } - internal class NewTournamentStorage : WrappedStorage + internal class TournamentStorage : WrappedStorage { private readonly GameHost host; private readonly TournamentStorageManager storageConfig; public readonly TournamentVideoStorage VideoStorage; - public NewTournamentStorage(GameHost host) + public TournamentStorage(GameHost host) : base(host.Storage, string.Empty) { this.host = host; From ba5a747ac9e1b83ff38b28884d86952895d34ab9 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 03:03:57 +0200 Subject: [PATCH 006/350] Implement migration for TournamentStorage --- osu.Game.Tournament/TournamentStorage.cs | 75 ++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index e5d19831d0..48eef76a28 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; +using System.Threading; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IO; +using System.IO; using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament @@ -32,16 +36,77 @@ namespace osu.Game.Tournament this.host = host; storageConfig = new TournamentStorageManager(host.Storage); - var customTournamentPath = storageConfig.Get(StorageConfig.CurrentTournament); + var currentTournament = storageConfig.Get(StorageConfig.CurrentTournament); - if (!string.IsNullOrEmpty(customTournamentPath)) + if (!string.IsNullOrEmpty(currentTournament)) { - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments/" + customTournamentPath)); - } else { - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments/default")); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments" + Path.DirectorySeparatorChar + currentTournament)); } + else + { + // Migrating old storage format to the new one. + Migrate(); + Logger.Log("Migrating files from old storage to new."); + } + VideoStorage = new TournamentVideoStorage(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } + + private void Migrate() + { + var defaultPath = "tournaments/default"; + var source = new DirectoryInfo(GetFullPath("tournament")); + var destination = new DirectoryInfo(GetFullPath(defaultPath)); + + Directory.CreateDirectory(destination.FullName); + + if (host.Storage.Exists("bracket.json")) + { + Logger.Log("Migrating bracket to default tournament storage."); + var bracketFile = new System.IO.FileInfo(GetFullPath(string.Empty) + Path.DirectorySeparatorChar + GetFiles(string.Empty, "bracket.json").First()); + attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); + } + + Logger.Log("Migrating other assets to default tournament storage."); + copyRecursive(source, destination); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(defaultPath)); + storageConfig.Set(StorageConfig.CurrentTournament, defaultPath); + storageConfig.Save(); + } + + private void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + { + // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo + + foreach (System.IO.FileInfo fi in source.GetFiles()) + { + attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); + } + + foreach (DirectoryInfo dir in source.GetDirectories()) + { + copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + } + } + + private void attemptOperation(Action action, int attempts = 10) + { + while (true) + { + try + { + action(); + return; + } + catch (Exception) + { + if (attempts-- == 0) + throw; + } + + Thread.Sleep(250); + } + } } } From f01a86f5b1c4395e99aec8a37fe9aa7c2563c5dd Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 03:12:37 +0200 Subject: [PATCH 007/350] Fix styling issues and move StorageManager to Configuration Folder --- .../TournamentStorageManager.cs | 1 - osu.Game.Tournament/TournamentGameBase.cs | 4 +--- osu.Game.Tournament/TournamentStorage.cs | 16 ++++++++-------- 3 files changed, 9 insertions(+), 12 deletions(-) rename osu.Game.Tournament/{ => Configuration}/TournamentStorageManager.cs (99%) diff --git a/osu.Game.Tournament/TournamentStorageManager.cs b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs similarity index 99% rename from osu.Game.Tournament/TournamentStorageManager.cs rename to osu.Game.Tournament/Configuration/TournamentStorageManager.cs index b1f84ecf44..6ccc2b6308 100644 --- a/osu.Game.Tournament/TournamentStorageManager.cs +++ b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs @@ -20,7 +20,6 @@ namespace osu.Game.Tournament.Configuration base.InitialiseDefaults(); Set(StorageConfig.CurrentTournament, string.Empty); } - } public enum StorageConfig diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 991c586a56..e3d310a497 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -19,8 +19,6 @@ using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API.Requests; -using osu.Framework.Logging; -using osu.Game.Tournament.Configuration; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; using osu.Game.Users; @@ -62,7 +60,7 @@ namespace osu.Game.Tournament Textures.AddStore(new TextureLoaderStore(tournamentStorage.VideoStorage)); - this.storage = tournamentStorage; + storage = tournamentStorage; windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index 48eef76a28..d1c8635466 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament else { // Migrating old storage format to the new one. - Migrate(); + migrate(); Logger.Log("Migrating files from old storage to new."); } @@ -53,16 +53,16 @@ namespace osu.Game.Tournament Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - private void Migrate() + private void migrate() { - var defaultPath = "tournaments/default"; + const string default_path = "tournaments/default"; var source = new DirectoryInfo(GetFullPath("tournament")); - var destination = new DirectoryInfo(GetFullPath(defaultPath)); + var destination = new DirectoryInfo(GetFullPath(default_path)); Directory.CreateDirectory(destination.FullName); - + if (host.Storage.Exists("bracket.json")) - { + { Logger.Log("Migrating bracket to default tournament storage."); var bracketFile = new System.IO.FileInfo(GetFullPath(string.Empty) + Path.DirectorySeparatorChar + GetFiles(string.Empty, "bracket.json").First()); attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); @@ -70,8 +70,8 @@ namespace osu.Game.Tournament Logger.Log("Migrating other assets to default tournament storage."); copyRecursive(source, destination); - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(defaultPath)); - storageConfig.Set(StorageConfig.CurrentTournament, defaultPath); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_path)); + storageConfig.Set(StorageConfig.CurrentTournament, default_path); storageConfig.Save(); } From ce66b723908356476fdce35691e390a1e3a8b2b5 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 18:25:20 +0200 Subject: [PATCH 008/350] Refactor paths --- osu.Game.Tournament/TournamentStorage.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index d1c8635466..32dd904b2f 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tournament public readonly TournamentVideoStorage VideoStorage; public TournamentStorage(GameHost host) - : base(host.Storage, string.Empty) + : base(host.Storage.GetStorageForDirectory("tournaments"), string.Empty) { this.host = host; @@ -40,11 +40,10 @@ namespace osu.Game.Tournament if (!string.IsNullOrEmpty(currentTournament)) { - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments" + Path.DirectorySeparatorChar + currentTournament)); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(currentTournament)); } else { - // Migrating old storage format to the new one. migrate(); Logger.Log("Migrating files from old storage to new."); } @@ -55,8 +54,8 @@ namespace osu.Game.Tournament private void migrate() { - const string default_path = "tournaments/default"; - var source = new DirectoryInfo(GetFullPath("tournament")); + const string default_path = "default"; + var source = new DirectoryInfo(host.Storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_path)); Directory.CreateDirectory(destination.FullName); @@ -64,7 +63,7 @@ namespace osu.Game.Tournament if (host.Storage.Exists("bracket.json")) { Logger.Log("Migrating bracket to default tournament storage."); - var bracketFile = new System.IO.FileInfo(GetFullPath(string.Empty) + Path.DirectorySeparatorChar + GetFiles(string.Empty, "bracket.json").First()); + var bracketFile = new System.IO.FileInfo(host.Storage.GetFullPath("bracket.json")); attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); } From d2ae146c1ffb56dcb6eababc9df624008d50a589 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 19:51:44 +0200 Subject: [PATCH 009/350] Remove unnecessary parameters and implement delete --- osu.Game.Tournament/TournamentStorage.cs | 42 ++++++++++++++++-------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index 32dd904b2f..87a2604d0b 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -13,17 +13,6 @@ using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament { - internal class TournamentVideoStorage : NamespacedResourceStore - { - public TournamentVideoStorage(Storage storage) - : base(new StorageBackedResourceStore(storage), "videos") - { - AddExtension("m4v"); - AddExtension("avi"); - AddExtension("mp4"); - } - } - internal class TournamentStorage : WrappedStorage { private readonly GameHost host; @@ -72,9 +61,10 @@ namespace osu.Game.Tournament ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_path)); storageConfig.Set(StorageConfig.CurrentTournament, default_path); storageConfig.Save(); + deleteRecursive(source); } - private void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + private void copyRecursive(DirectoryInfo source, DirectoryInfo destination) { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo @@ -85,10 +75,26 @@ namespace osu.Game.Tournament foreach (DirectoryInfo dir in source.GetDirectories()) { - copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + copyRecursive(dir, destination.CreateSubdirectory(dir.Name)); } } + private void deleteRecursive(DirectoryInfo target) + { + foreach (System.IO.FileInfo fi in target.GetFiles()) + { + attemptOperation(() => fi.Delete()); + } + + foreach (DirectoryInfo dir in target.GetDirectories()) + { + attemptOperation(() => dir.Delete(true)); + } + + if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) + attemptOperation(target.Delete); + } + private void attemptOperation(Action action, int attempts = 10) { while (true) @@ -108,4 +114,14 @@ namespace osu.Game.Tournament } } } + internal class TournamentVideoStorage : NamespacedResourceStore + { + public TournamentVideoStorage(Storage storage) + : base(new StorageBackedResourceStore(storage), "videos") + { + AddExtension("m4v"); + AddExtension("avi"); + AddExtension("mp4"); + } + } } From 2f15d7fbac96b253cda0b9c1156e660c9c40dee6 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 20:04:38 +0200 Subject: [PATCH 010/350] Code styling fixes --- osu.Game.Tournament/TournamentStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index 87a2604d0b..49f3d69be1 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using System.Threading; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -114,6 +113,7 @@ namespace osu.Game.Tournament } } } + internal class TournamentVideoStorage : NamespacedResourceStore { public TournamentVideoStorage(Storage storage) From 417919320cfa8400146832ca44bcce33ca660e8b Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 9 Jun 2020 17:28:42 +0200 Subject: [PATCH 011/350] change namespace to osu.Game.Tournament.IO --- osu.Game.Tournament/Components/TourneyVideo.cs | 1 + osu.Game.Tournament/{ => IO}/TournamentStorage.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) rename osu.Game.Tournament/{ => IO}/TournamentStorage.cs (99%) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 131fa9450d..dcb08464dd 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; using osu.Framework.Timing; using osu.Game.Graphics; +using osu.Game.Tournament.IO; namespace osu.Game.Tournament.Components { diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs similarity index 99% rename from osu.Game.Tournament/TournamentStorage.cs rename to osu.Game.Tournament/IO/TournamentStorage.cs index 49f3d69be1..7690051c7a 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -10,7 +10,7 @@ using osu.Game.IO; using System.IO; using osu.Game.Tournament.Configuration; -namespace osu.Game.Tournament +namespace osu.Game.Tournament.IO { internal class TournamentStorage : WrappedStorage { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index e3d310a497..ccfbf37d48 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -20,6 +20,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; +using osu.Game.Tournament.IO; using osu.Game.Tournament.Models; using osu.Game.Users; using osuTK; From c2e01e198f1da6682bd8d1601ffa207509ee0dfc Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 13:55:29 +0200 Subject: [PATCH 012/350] Rename tournamentStorage to storage --- osu.Game.Tournament/TournamentGameBase.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index ccfbf37d48..4c0c8cc28f 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.IO.Stores; -using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API.Requests; @@ -36,9 +35,7 @@ namespace osu.Game.Tournament private LadderInfo ladder; - private Storage storage; - - private TournamentStorage tournamentStorage; + private TournamentStorage storage; private DependencyContainer dependencies; @@ -57,11 +54,9 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(tournamentStorage = new TournamentStorage(Host)); + dependencies.CacheAs(storage = new TournamentStorage(Host)); - Textures.AddStore(new TextureLoaderStore(tournamentStorage.VideoStorage)); - - storage = tournamentStorage; + Textures.AddStore(new TextureLoaderStore(storage.VideoStorage)); windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => From b69ff307d83beb9aa40d81e00b8d8c16ab0b5b88 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 13:56:16 +0200 Subject: [PATCH 013/350] Fixed migration logic --- osu.Game.Tournament/IO/TournamentStorage.cs | 33 ++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 7690051c7a..a0f07c354b 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,15 +15,16 @@ namespace osu.Game.Tournament.IO internal class TournamentStorage : WrappedStorage { private readonly GameHost host; - private readonly TournamentStorageManager storageConfig; public readonly TournamentVideoStorage VideoStorage; + private const string default_tournament = "default"; public TournamentStorage(GameHost host) : base(host.Storage.GetStorageForDirectory("tournaments"), string.Empty) { this.host = host; - storageConfig = new TournamentStorageManager(host.Storage); + TournamentStorageManager storageConfig = new TournamentStorageManager(host.Storage); + var currentTournament = storageConfig.Get(StorageConfig.CurrentTournament); if (!string.IsNullOrEmpty(currentTournament)) @@ -32,35 +33,39 @@ namespace osu.Game.Tournament.IO } else { - migrate(); Logger.Log("Migrating files from old storage to new."); + Migrate(); + storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); + storageConfig.Save(); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } VideoStorage = new TournamentVideoStorage(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - private void migrate() + internal void Migrate() { - const string default_path = "default"; var source = new DirectoryInfo(host.Storage.GetFullPath("tournament")); - var destination = new DirectoryInfo(GetFullPath(default_path)); + var destination = new DirectoryInfo(GetFullPath(default_tournament)); - Directory.CreateDirectory(destination.FullName); + if (!destination.Exists) + destination.Create(); if (host.Storage.Exists("bracket.json")) { Logger.Log("Migrating bracket to default tournament storage."); var bracketFile = new System.IO.FileInfo(host.Storage.GetFullPath("bracket.json")); attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); + bracketFile.Delete(); } - Logger.Log("Migrating other assets to default tournament storage."); - copyRecursive(source, destination); - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_path)); - storageConfig.Set(StorageConfig.CurrentTournament, default_path); - storageConfig.Save(); - deleteRecursive(source); + if (source.Exists) + { + Logger.Log("Migrating tournament assets to default tournament storage."); + copyRecursive(source, destination); + deleteRecursive(source); + } } private void copyRecursive(DirectoryInfo source, DirectoryInfo destination) @@ -113,7 +118,7 @@ namespace osu.Game.Tournament.IO } } } - + internal class TournamentVideoStorage : NamespacedResourceStore { public TournamentVideoStorage(Storage storage) From 18a9e5a0a6e52069dba001144f26436c8e240d4a Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 13:57:29 +0200 Subject: [PATCH 014/350] Add NonVisual tests for custom tournaments Can test the default directory from a clean instance, it can test a custom directory and can execute migration from an instance using the older directory setup. --- .../NonVisual/CustomTourneyDirectoryTest.cs | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs new file mode 100644 index 0000000000..757465d4ad --- /dev/null +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Tournament.Configuration; +using osu.Game.Tournament.IO; +using osu.Game.Tests; + +namespace osu.Game.Tournament.Tests.NonVisual +{ + [TestFixture] + public class CustomTourneyDirectoryTest + { + [SetUp] + public void SetUp() + { + } + + [Test] + public void TestDefaultDirectory() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory))) + { + try + { + var osu = loadOsu(host); + var storage = osu.Dependencies.Get(); + var defaultStorage = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory), "tournaments", "default"); + Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorage)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestCustomDirectory() + { + using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) + { + string osuDesktopStorage = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory)); + const string custom_tournament = "custom"; + + // need access before the game has constructed its own storage yet. + Storage storage = new DesktopStorage(osuDesktopStorage, host); + // manual cleaning so we can prepare a config file. + storage.DeleteDirectory(string.Empty); + + using (var storageConfig = new TournamentStorageManager(storage)) + storageConfig.Set(StorageConfig.CurrentTournament, custom_tournament); + + try + { + var osu = loadOsu(host); + + storage = osu.Dependencies.Get(); + + Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), "custom"))); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestMigration() + { + using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) + { + string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournament"); + + string videosPath = Path.Combine(basePath, "videos"); + string modsPath = Path.Combine(basePath, "mods"); + string flagsPath = Path.Combine(basePath, "flags"); + + Directory.CreateDirectory(videosPath); + Directory.CreateDirectory(modsPath); + Directory.CreateDirectory(flagsPath); + + string bracketFile = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "bracket.json"); + string videoFile = Path.Combine(videosPath, "video.mp4"); + string modFile = Path.Combine(modsPath, "mod.png"); + string flagFile = Path.Combine(flagsPath, "flag.png"); + + File.WriteAllText(bracketFile, "{}"); + + File.WriteAllText(videoFile, "test"); + File.WriteAllText(modFile, "test"); + File.WriteAllText(flagFile, "test"); + + try + { + var osu = loadOsu(host); + + var storage = osu.Dependencies.Get(); + + var migratedPath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournaments", "default"); + + videosPath = Path.Combine(migratedPath, "videos"); + modsPath = Path.Combine(migratedPath, "mods"); + flagsPath = Path.Combine(migratedPath, "flags"); + + videoFile = Path.Combine(videosPath, "video.mp4"); + modFile = Path.Combine(modsPath, "mod.png"); + flagFile = Path.Combine(flagsPath, "flag.png"); + + Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath)); + Assert.That(storage.GetFiles(".", "bracket.json").Single(), Is.EqualTo("bracket.json")); + Assert.True(storage.Exists(videoFile)); + Assert.True(storage.Exists(modFile)); + Assert.True(storage.Exists(flagFile)); + } + finally + { + // Cleaning up after ourselves. + host.Storage.Delete("tournament.ini"); + host.Storage.DeleteDirectory("tournaments"); + + host.Exit(); + } + } + } + + private TournamentGameBase loadOsu(GameHost host) + { + var osu = new TournamentGameBase(); + Task.Run(() => host.Run(osu)); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + return osu; + } + + private static void waitForOrAssert(Func result, string failureMessage, int timeout = 90000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + + private string oldPath => Path.Combine(RuntimeInfo.StartupDirectory, "tournament"); + private string tournamentBasePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance, "tournaments"); + } +} \ No newline at end of file From a317b85fd823f1988a1514b93b99e59c866ea8ff Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 14:06:03 +0200 Subject: [PATCH 015/350] Remove misleading log --- osu.Game.Tournament/IO/TournamentStorage.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index a0f07c354b..2379967125 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -33,7 +33,6 @@ namespace osu.Game.Tournament.IO } else { - Logger.Log("Migrating files from old storage to new."); Migrate(); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); From 5d49b709b99f5a8d1da59cfc76e5025423cd5977 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 14:09:21 +0200 Subject: [PATCH 016/350] Change access modifier public -> internal --- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 2379967125..c6f314032f 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tournament.IO internal class TournamentStorage : WrappedStorage { private readonly GameHost host; - public readonly TournamentVideoStorage VideoStorage; + internal readonly TournamentVideoStorage VideoStorage; private const string default_tournament = "default"; public TournamentStorage(GameHost host) From 2964b457a01894c559c329d9ea98f895e66a9b55 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:05:28 +0200 Subject: [PATCH 017/350] Rename VideoStorage to VideoStore --- osu.Game.Tournament/Components/TourneyVideo.cs | 2 +- osu.Game.Tournament/IO/TournamentStorage.cs | 8 ++++---- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index dcb08464dd..5a595f4f44 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Components [BackgroundDependencyLoader] private void load(TournamentStorage storage) { - var stream = storage.VideoStorage.GetStream($@"{filename}"); + var stream = storage.VideoStore.GetStream($@"{filename}"); if (stream != null) { diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index c6f314032f..2eb052a2e3 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tournament.IO internal class TournamentStorage : WrappedStorage { private readonly GameHost host; - internal readonly TournamentVideoStorage VideoStorage; + internal readonly TournamentVideoResourceStore VideoStore; private const string default_tournament = "default"; public TournamentStorage(GameHost host) @@ -39,7 +39,7 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } - VideoStorage = new TournamentVideoStorage(this); + VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } @@ -118,9 +118,9 @@ namespace osu.Game.Tournament.IO } } - internal class TournamentVideoStorage : NamespacedResourceStore + internal class TournamentVideoResourceStore : NamespacedResourceStore { - public TournamentVideoStorage(Storage storage) + public TournamentVideoResourceStore(Storage storage) : base(new StorageBackedResourceStore(storage), "videos") { AddExtension("m4v"); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 4c0c8cc28f..9716f0cd5f 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tournament dependencies.CacheAs(storage = new TournamentStorage(Host)); - Textures.AddStore(new TextureLoaderStore(storage.VideoStorage)); + Textures.AddStore(new TextureLoaderStore(storage.VideoStore)); windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => From af1bbe78578f7388c7af7b08e42969038d9333e1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:13:19 +0200 Subject: [PATCH 018/350] move TournamentVideoResourceStore to separate file --- osu.Game.Tournament/IO/TournamentStorage.cs | 12 ------------ .../IO/TournamentVideoResourceStore.cs | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Tournament/IO/TournamentVideoResourceStore.cs diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 2eb052a2e3..ab7a5f63d2 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -3,7 +3,6 @@ using System; using System.Threading; -using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IO; @@ -117,15 +116,4 @@ namespace osu.Game.Tournament.IO } } } - - internal class TournamentVideoResourceStore : NamespacedResourceStore - { - public TournamentVideoResourceStore(Storage storage) - : base(new StorageBackedResourceStore(storage), "videos") - { - AddExtension("m4v"); - AddExtension("avi"); - AddExtension("mp4"); - } - } } diff --git a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs new file mode 100644 index 0000000000..6a44240f65 --- /dev/null +++ b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.IO.Stores; +using osu.Framework.Platform; + +namespace osu.Game.Tournament.IO +{ + internal class TournamentVideoResourceStore : NamespacedResourceStore + { + public TournamentVideoResourceStore(Storage storage) + : base(new StorageBackedResourceStore(storage), "videos") + { + AddExtension("m4v"); + AddExtension("avi"); + AddExtension("mp4"); + } + } +} \ No newline at end of file From 883185d3497832ab7309bb566f31010c8ef9a883 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:18:21 +0200 Subject: [PATCH 019/350] Add a comment to describe what's going on before the headless game starts --- .../NonVisual/CustomTourneyDirectoryTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 757465d4ad..867851e06b 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -79,6 +79,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) { + // Recreate the old setup that uses "tournament" as the base path. string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournament"); string videosPath = Path.Combine(basePath, "videos"); From 603054f5214beb6b4ae744d62e7b61c5a03e8f14 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:47:21 +0200 Subject: [PATCH 020/350] Remove unused property and reuse tournamentBasePath --- .../NonVisual/CustomTourneyDirectoryTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 867851e06b..7ac4e51711 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - var defaultStorage = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory), "tournaments", "default"); + var defaultStorage = Path.Combine(tournamentBasePath(nameof(TestDefaultDirectory)), "default"); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorage)); } finally @@ -107,7 +107,7 @@ namespace osu.Game.Tournament.Tests.NonVisual var storage = osu.Dependencies.Get(); - var migratedPath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournaments", "default"); + var migratedPath = Path.Combine(tournamentBasePath(nameof(TestMigration)), "default"); videosPath = Path.Combine(migratedPath, "videos"); modsPath = Path.Combine(migratedPath, "mods"); @@ -151,8 +151,6 @@ namespace osu.Game.Tournament.Tests.NonVisual Assert.IsTrue(task.Wait(timeout), failureMessage); } - - private string oldPath => Path.Combine(RuntimeInfo.StartupDirectory, "tournament"); private string tournamentBasePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance, "tournaments"); } } \ No newline at end of file From 222ac863042e8367b8e4a582eabafb453a3a0531 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:52:14 +0200 Subject: [PATCH 021/350] Add newlines at the end of the file --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 1 + .../NonVisual/CustomTourneyDirectoryTest.cs | 2 +- osu.Game.Tournament/Configuration/TournamentStorageManager.cs | 2 +- osu.Game.Tournament/IO/TournamentVideoResourceStore.cs | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index f3d54d876a..5f0ca303e3 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -293,3 +293,4 @@ namespace osu.Game.Tests.NonVisual } } } + diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 7ac4e51711..851efb9a3d 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -153,4 +153,4 @@ namespace osu.Game.Tournament.Tests.NonVisual } private string tournamentBasePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance, "tournaments"); } -} \ No newline at end of file +} diff --git a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs index 6ccc2b6308..653ea14352 100644 --- a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs +++ b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs @@ -26,4 +26,4 @@ namespace osu.Game.Tournament.Configuration { CurrentTournament, } -} \ No newline at end of file +} diff --git a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs index 6a44240f65..1ccd20fe21 100644 --- a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs +++ b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs @@ -16,4 +16,4 @@ namespace osu.Game.Tournament.IO AddExtension("mp4"); } } -} \ No newline at end of file +} From 1d4d749b539490f295036c00aa9740fb6d2403f4 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:56:34 +0200 Subject: [PATCH 022/350] Undo blank line removal Was too excited to add blank lines before submitting the PR that I overdid it --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 5f0ca303e3..f3d54d876a 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -293,4 +293,3 @@ namespace osu.Game.Tests.NonVisual } } } - From c9dc17f3d8868679bb99b37ea425dff4624626eb Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 17:51:07 +0200 Subject: [PATCH 023/350] Introduce migrations for drawings --- osu.Game.Tournament/IO/TournamentStorage.cs | 23 +++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index ab7a5f63d2..0879d27aac 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -54,8 +54,21 @@ namespace osu.Game.Tournament.IO { Logger.Log("Migrating bracket to default tournament storage."); var bracketFile = new System.IO.FileInfo(host.Storage.GetFullPath("bracket.json")); - attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); - bracketFile.Delete(); + moveFile(bracketFile, destination); + } + + if (host.Storage.Exists("drawings.txt")) + { + Logger.Log("Migrating drawings to default tournament storage."); + var drawingsFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings.txt")); + moveFile(drawingsFile, destination); + } + + if (host.Storage.Exists("drawings_results.txt")) + { + Logger.Log("Migrating drawings results to default tournament storage."); + var drawingsResultsFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings_results.txt")); + moveFile(drawingsResultsFile, destination); } if (source.Exists) @@ -97,6 +110,12 @@ namespace osu.Game.Tournament.IO attemptOperation(target.Delete); } + private void moveFile(System.IO.FileInfo file, DirectoryInfo destination) + { + attemptOperation(() => file.CopyTo(Path.Combine(destination.FullName, file.Name), true)); + file.Delete(); + } + private void attemptOperation(Action action, int attempts = 10) { while (true) From 327795ba9933f8bcf7447c1b4f62ebfa40e763e0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 18:00:47 +0200 Subject: [PATCH 024/350] Switch drawing storage to tournamentstorage --- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- .../Screens/Drawings/Components/StorageBackedTeamList.cs | 6 +++--- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 0879d27aac..195448f2d5 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -11,7 +11,7 @@ using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament.IO { - internal class TournamentStorage : WrappedStorage + public class TournamentStorage : WrappedStorage { private readonly GameHost host; internal readonly TournamentVideoResourceStore VideoStore; diff --git a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index f96ec01cbb..ecc23181be 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using osu.Framework.Logging; -using osu.Framework.Platform; +using osu.Game.Tournament.IO; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Screens.Drawings.Components @@ -14,9 +14,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { private const string teams_filename = "drawings.txt"; - private readonly Storage storage; + private readonly TournamentStorage storage; - public StorageBackedTeamList(Storage storage) + public StorageBackedTeamList(TournamentStorage storage) { this.storage = storage; } diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 8be66ff98c..bf0d6f4871 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -12,9 +12,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; -using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Tournament.Components; +using osu.Game.Tournament.IO; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Drawings.Components; using osuTK; @@ -36,12 +36,12 @@ namespace osu.Game.Tournament.Screens.Drawings private Task writeOp; - private Storage storage; + private TournamentStorage storage; public ITeamList TeamList; [BackgroundDependencyLoader] - private void load(TextureStore textures, Storage storage) + private void load(TextureStore textures, TournamentStorage storage) { RelativeSizeAxes = Axes.Both; From 32d86d6fab0a34f502de05fc5becec996b0947f0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 18:07:24 +0200 Subject: [PATCH 025/350] Create storage for config files of a tournament --- osu.Game.Tournament/IO/TournamentStorage.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 195448f2d5..b658dfdb69 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,6 +15,7 @@ namespace osu.Game.Tournament.IO { private readonly GameHost host; internal readonly TournamentVideoResourceStore VideoStore; + internal readonly Storage ConfigurationStorage; private const string default_tournament = "default"; public TournamentStorage(GameHost host) @@ -38,6 +39,8 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } + ConfigurationStorage = UnderlyingStorage.GetStorageForDirectory("config"); + VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } From 592e3bf4c91bc6f976a80d1b416309c60503bcac Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 18:21:56 +0200 Subject: [PATCH 026/350] Implement migrations for the drawings config file --- osu.Game.Tournament/IO/TournamentStorage.cs | 14 +++++++++++++- .../Screens/Drawings/DrawingsScreen.cs | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index b658dfdb69..298d02e6bb 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -17,6 +17,7 @@ namespace osu.Game.Tournament.IO internal readonly TournamentVideoResourceStore VideoStore; internal readonly Storage ConfigurationStorage; private const string default_tournament = "default"; + private const string config_directory = "config"; public TournamentStorage(GameHost host) : base(host.Storage.GetStorageForDirectory("tournaments"), string.Empty) @@ -39,7 +40,7 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } - ConfigurationStorage = UnderlyingStorage.GetStorageForDirectory("config"); + ConfigurationStorage = UnderlyingStorage.GetStorageForDirectory(config_directory); VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); @@ -49,9 +50,13 @@ namespace osu.Game.Tournament.IO { var source = new DirectoryInfo(host.Storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_tournament)); + var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); if (!destination.Exists) destination.Create(); + + if (!cfgDestination.Exists) + destination.CreateSubdirectory(config_directory); if (host.Storage.Exists("bracket.json")) { @@ -67,6 +72,13 @@ namespace osu.Game.Tournament.IO moveFile(drawingsFile, destination); } + if (host.Storage.Exists("drawings.ini")) + { + Logger.Log("Migrating drawing configuration to default tournament storage."); + var drawingsConfigFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings.ini")); + moveFile(drawingsConfigFile, cfgDestination); + } + if (host.Storage.Exists("drawings_results.txt")) { Logger.Log("Migrating drawings results to default tournament storage."); diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index bf0d6f4871..7f2563c948 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tournament.Screens.Drawings return; } - drawingsConfig = new DrawingsConfigManager(storage); + drawingsConfig = new DrawingsConfigManager(storage.ConfigurationStorage); InternalChildren = new Drawable[] { From 56a40e616b0688a4969577a6b5b540854556b8bc Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 20:11:44 +0200 Subject: [PATCH 027/350] Add drawings to the migration test --- .../NonVisual/CustomTourneyDirectoryTest.cs | 40 ++++++++++++++----- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 851efb9a3d..37f456ae96 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -48,7 +47,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) { - string osuDesktopStorage = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory)); + string osuDesktopStorage = basePath(nameof(TestCustomDirectory)); const string custom_tournament = "custom"; // need access before the game has constructed its own storage yet. @@ -80,23 +79,34 @@ namespace osu.Game.Tournament.Tests.NonVisual using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) { // Recreate the old setup that uses "tournament" as the base path. - string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournament"); + string osuRoot = basePath(nameof(TestMigration)); - string videosPath = Path.Combine(basePath, "videos"); - string modsPath = Path.Combine(basePath, "mods"); - string flagsPath = Path.Combine(basePath, "flags"); + // Define all the paths for the old scenario + string oldPath = Path.Combine(osuRoot, "tournament"); + string videosPath = Path.Combine(oldPath, "videos"); + string modsPath = Path.Combine(oldPath, "mods"); + string flagsPath = Path.Combine(oldPath, "flags"); Directory.CreateDirectory(videosPath); Directory.CreateDirectory(modsPath); Directory.CreateDirectory(flagsPath); - string bracketFile = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "bracket.json"); + // Define testing files corresponding to the specific file migrations that are needed + string bracketFile = Path.Combine(osuRoot, "bracket.json"); + + string drawingsConfig = Path.Combine(osuRoot, "drawings.ini"); + string drawingsFile = Path.Combine(osuRoot, "drawings.txt"); + string drawingsResult = Path.Combine(osuRoot, "drawings_results.txt"); + + // Define sample files to test recursive copying string videoFile = Path.Combine(videosPath, "video.mp4"); string modFile = Path.Combine(modsPath, "mod.png"); string flagFile = Path.Combine(flagsPath, "flag.png"); File.WriteAllText(bracketFile, "{}"); - + File.WriteAllText(drawingsConfig, "test"); + File.WriteAllText(drawingsFile, "test"); + File.WriteAllText(drawingsResult, "test"); File.WriteAllText(videoFile, "test"); File.WriteAllText(modFile, "test"); File.WriteAllText(flagFile, "test"); @@ -118,7 +128,13 @@ namespace osu.Game.Tournament.Tests.NonVisual flagFile = Path.Combine(flagsPath, "flag.png"); Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath)); - Assert.That(storage.GetFiles(".", "bracket.json").Single(), Is.EqualTo("bracket.json")); + + Assert.True(storage.Exists("bracket.json")); + Assert.True(storage.Exists("drawings.txt")); + Assert.True(storage.Exists("drawings_results.txt")); + + Assert.True(storage.ConfigurationStorage.Exists("drawings.ini")); + Assert.True(storage.Exists(videoFile)); Assert.True(storage.Exists(modFile)); Assert.True(storage.Exists(flagFile)); @@ -128,7 +144,6 @@ namespace osu.Game.Tournament.Tests.NonVisual // Cleaning up after ourselves. host.Storage.Delete("tournament.ini"); host.Storage.DeleteDirectory("tournaments"); - host.Exit(); } } @@ -151,6 +166,9 @@ namespace osu.Game.Tournament.Tests.NonVisual Assert.IsTrue(task.Wait(timeout), failureMessage); } - private string tournamentBasePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance, "tournaments"); + + private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance); + + private string tournamentBasePath(string testInstance) => Path.Combine(basePath(testInstance), "tournaments"); } } diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 298d02e6bb..05ee7a3618 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tournament.IO if (!destination.Exists) destination.Create(); - + if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); From 5041c74c7a525de4261b2bf800f97222cd4ede4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 11:30:15 +0900 Subject: [PATCH 028/350] Fix merge issue --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index bc9999381b..a779135345 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament } [BackgroundDependencyLoader] - private void load(Storage storage) + private void load() { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); From 29ae1c460aa9564c6fc04ffa0d001d1139c42c43 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 17:00:20 +0200 Subject: [PATCH 029/350] TournamentStorage now takes in a parent storage --- osu.Game.Tournament/IO/TournamentStorage.cs | 28 ++++++++++----------- osu.Game.Tournament/TournamentGameBase.cs | 8 +++--- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 05ee7a3618..d6e95bc4b6 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -13,18 +13,18 @@ namespace osu.Game.Tournament.IO { public class TournamentStorage : WrappedStorage { - private readonly GameHost host; + private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; internal readonly Storage ConfigurationStorage; private const string default_tournament = "default"; private const string config_directory = "config"; - public TournamentStorage(GameHost host) - : base(host.Storage.GetStorageForDirectory("tournaments"), string.Empty) + public TournamentStorage(Storage storage) + : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { - this.host = host; + this.storage = storage; - TournamentStorageManager storageConfig = new TournamentStorageManager(host.Storage); + TournamentStorageManager storageConfig = new TournamentStorageManager(storage); var currentTournament = storageConfig.Get(StorageConfig.CurrentTournament); @@ -48,7 +48,7 @@ namespace osu.Game.Tournament.IO internal void Migrate() { - var source = new DirectoryInfo(host.Storage.GetFullPath("tournament")); + var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_tournament)); var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); @@ -58,31 +58,31 @@ namespace osu.Game.Tournament.IO if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); - if (host.Storage.Exists("bracket.json")) + if (storage.Exists("bracket.json")) { Logger.Log("Migrating bracket to default tournament storage."); - var bracketFile = new System.IO.FileInfo(host.Storage.GetFullPath("bracket.json")); + var bracketFile = new System.IO.FileInfo(storage.GetFullPath("bracket.json")); moveFile(bracketFile, destination); } - if (host.Storage.Exists("drawings.txt")) + if (storage.Exists("drawings.txt")) { Logger.Log("Migrating drawings to default tournament storage."); - var drawingsFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings.txt")); + var drawingsFile = new System.IO.FileInfo(storage.GetFullPath("drawings.txt")); moveFile(drawingsFile, destination); } - if (host.Storage.Exists("drawings.ini")) + if (storage.Exists("drawings.ini")) { Logger.Log("Migrating drawing configuration to default tournament storage."); - var drawingsConfigFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings.ini")); + var drawingsConfigFile = new System.IO.FileInfo(storage.GetFullPath("drawings.ini")); moveFile(drawingsConfigFile, cfgDestination); } - if (host.Storage.Exists("drawings_results.txt")) + if (storage.Exists("drawings_results.txt")) { Logger.Log("Migrating drawings results to default tournament storage."); - var drawingsResultsFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings_results.txt")); + var drawingsResultsFile = new System.IO.FileInfo(storage.GetFullPath("drawings_results.txt")); moveFile(drawingsResultsFile, destination); } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index a779135345..7ec8d0f18a 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.Platform; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests; @@ -23,11 +24,8 @@ namespace osu.Game.Tournament public class TournamentGameBase : OsuGameBase { private const string bracket_filename = "bracket.json"; - private LadderInfo ladder; - private TournamentStorage storage; - private DependencyContainer dependencies; private FileBasedIPC ipc; @@ -37,11 +35,11 @@ namespace osu.Game.Tournament } [BackgroundDependencyLoader] - private void load() + private void load(Storage baseStorage) { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(storage = new TournamentStorage(Host)); + dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); Textures.AddStore(new TextureLoaderStore(storage.VideoStore)); From b75fd7bfa8756addf028665492b456334d55b620 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 17:14:54 +0200 Subject: [PATCH 030/350] Refactor moving logic (1/2) --- osu.Game.Tournament/IO/TournamentStorage.cs | 43 +++++++-------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index d6e95bc4b6..1962cc46d8 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -57,34 +57,11 @@ namespace osu.Game.Tournament.IO if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); - - if (storage.Exists("bracket.json")) - { - Logger.Log("Migrating bracket to default tournament storage."); - var bracketFile = new System.IO.FileInfo(storage.GetFullPath("bracket.json")); - moveFile(bracketFile, destination); - } - - if (storage.Exists("drawings.txt")) - { - Logger.Log("Migrating drawings to default tournament storage."); - var drawingsFile = new System.IO.FileInfo(storage.GetFullPath("drawings.txt")); - moveFile(drawingsFile, destination); - } - - if (storage.Exists("drawings.ini")) - { - Logger.Log("Migrating drawing configuration to default tournament storage."); - var drawingsConfigFile = new System.IO.FileInfo(storage.GetFullPath("drawings.ini")); - moveFile(drawingsConfigFile, cfgDestination); - } - - if (storage.Exists("drawings_results.txt")) - { - Logger.Log("Migrating drawings results to default tournament storage."); - var drawingsResultsFile = new System.IO.FileInfo(storage.GetFullPath("drawings_results.txt")); - moveFile(drawingsResultsFile, destination); - } + + moveFileIfExists("bracket.json", destination); + moveFileIfExists("drawings.txt", destination); + moveFileIfExists("drawings_results.txt", destination); + moveFileIfExists("drawings.ini", cfgDestination); if (source.Exists) { @@ -94,6 +71,16 @@ namespace osu.Game.Tournament.IO } } + private void moveFileIfExists(string file, DirectoryInfo destination) + { + if (storage.Exists(file)) + { + Logger.Log($"Migrating {file} to default tournament storage."); + var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); + moveFile(fileInfo, destination); + } + } + private void copyRecursive(DirectoryInfo source, DirectoryInfo destination) { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo From 02d66c4856626e60226e1a4ebcac6dffdcef13a7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 17:15:43 +0200 Subject: [PATCH 031/350] Refactor moving (2/2) --- osu.Game.Tournament/IO/TournamentStorage.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 1962cc46d8..611592e0e3 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tournament.IO if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); - + moveFileIfExists("bracket.json", destination); moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); @@ -77,7 +77,8 @@ namespace osu.Game.Tournament.IO { Logger.Log($"Migrating {file} to default tournament storage."); var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); - moveFile(fileInfo, destination); + attemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); + fileInfo.Delete(); } } @@ -112,12 +113,6 @@ namespace osu.Game.Tournament.IO attemptOperation(target.Delete); } - private void moveFile(System.IO.FileInfo file, DirectoryInfo destination) - { - attemptOperation(() => file.CopyTo(Path.Combine(destination.FullName, file.Name), true)); - file.Delete(); - } - private void attemptOperation(Action action, int attempts = 10) { while (true) From dd9697032c0071931eecf6b563c1774647ae33dd Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 17:39:20 +0200 Subject: [PATCH 032/350] Introduce new class MigratableStorage --- osu.Game.Tournament/IO/TournamentStorage.cs | 70 +++------------ osu.Game/IO/MigratableStorage.cs | 95 +++++++++++++++++++++ 2 files changed, 105 insertions(+), 60 deletions(-) create mode 100644 osu.Game/IO/MigratableStorage.cs diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 611592e0e3..1731b96095 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -11,7 +11,7 @@ using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament.IO { - public class TournamentStorage : WrappedStorage + public class TournamentStorage : MigratableStorage { private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; @@ -52,8 +52,15 @@ namespace osu.Game.Tournament.IO var destination = new DirectoryInfo(GetFullPath(default_tournament)); var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); - if (!destination.Exists) - destination.Create(); + // if (!destination.Exists) + // destination.Create(); + + if (source.Exists) + { + Logger.Log("Migrating tournament assets to default tournament storage."); + copyRecursive(source, destination); + deleteRecursive(source); + } if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); @@ -62,13 +69,6 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", cfgDestination); - - if (source.Exists) - { - Logger.Log("Migrating tournament assets to default tournament storage."); - copyRecursive(source, destination); - deleteRecursive(source); - } } private void moveFileIfExists(string file, DirectoryInfo destination) @@ -81,55 +81,5 @@ namespace osu.Game.Tournament.IO fileInfo.Delete(); } } - - private void copyRecursive(DirectoryInfo source, DirectoryInfo destination) - { - // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo - - foreach (System.IO.FileInfo fi in source.GetFiles()) - { - attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); - } - - foreach (DirectoryInfo dir in source.GetDirectories()) - { - copyRecursive(dir, destination.CreateSubdirectory(dir.Name)); - } - } - - private void deleteRecursive(DirectoryInfo target) - { - foreach (System.IO.FileInfo fi in target.GetFiles()) - { - attemptOperation(() => fi.Delete()); - } - - foreach (DirectoryInfo dir in target.GetDirectories()) - { - attemptOperation(() => dir.Delete(true)); - } - - if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - attemptOperation(target.Delete); - } - - private void attemptOperation(Action action, int attempts = 10) - { - while (true) - { - try - { - action(); - return; - } - catch (Exception) - { - if (attempts-- == 0) - throw; - } - - Thread.Sleep(250); - } - } } } diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs new file mode 100644 index 0000000000..0ab0ea9934 --- /dev/null +++ b/osu.Game/IO/MigratableStorage.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Configuration; + +namespace osu.Game.IO +{ + public abstract class MigratableStorage : WrappedStorage + { + + virtual protected string[] IGNORE_DIRECTORIES { get; set; } = Array.Empty(); + + virtual protected string[] IGNORE_FILES { get; set; } = Array.Empty(); + + public MigratableStorage(Storage storage, string subPath = null) + : base(storage, subPath) + { + } + + protected void deleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) + { + foreach (System.IO.FileInfo fi in target.GetFiles()) + { + if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) + continue; + + attemptOperation(() => fi.Delete()); + } + + foreach (DirectoryInfo dir in target.GetDirectories()) + { + if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) + continue; + + attemptOperation(() => dir.Delete(true)); + } + + if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) + attemptOperation(target.Delete); + } + + protected void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + { + // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo + if (!destination.Exists) + Directory.CreateDirectory(destination.FullName); + + foreach (System.IO.FileInfo fi in source.GetFiles()) + { + if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) + continue; + + attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); + } + + foreach (DirectoryInfo dir in source.GetDirectories()) + { + if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) + continue; + + copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + } + } + + /// + /// Attempt an IO operation multiple times and only throw if none of the attempts succeed. + /// + /// The action to perform. + /// The number of attempts (250ms wait between each). + protected static void attemptOperation(Action action, int attempts = 10) + { + while (true) + { + try + { + action(); + return; + } + catch (Exception) + { + if (attempts-- == 0) + throw; + } + + Thread.Sleep(250); + } + } + } +} From 21774b8967ffd8dfd99c1f29b9179394197599f5 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 11:38:50 +0200 Subject: [PATCH 033/350] Move static properties to parent class and inherit OsuStorage from it --- .../NonVisual/CustomDataDirectoryTest.cs | 4 +- osu.Game/IO/MigratableStorage.cs | 8 +- osu.Game/IO/OsuStorage.cs | 78 +------------------ 3 files changed, 9 insertions(+), 81 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index f3d54d876a..5abefe3198 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -151,13 +151,13 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in OsuStorage.IGNORE_FILES) + foreach (var file in MigratableStorage.IGNORE_FILES) { Assert.That(host.Storage.Exists(file), Is.True); Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in OsuStorage.IGNORE_DIRECTORIES) + foreach (var dir in MigratableStorage.IGNORE_DIRECTORIES) { Assert.That(host.Storage.ExistsDirectory(dir), Is.True); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 0ab0ea9934..004aa4e09a 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,9 +14,13 @@ namespace osu.Game.IO public abstract class MigratableStorage : WrappedStorage { - virtual protected string[] IGNORE_DIRECTORIES { get; set; } = Array.Empty(); + internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - virtual protected string[] IGNORE_FILES { get; set; } = Array.Empty(); + internal static readonly string[] IGNORE_FILES = + { + "framework.ini", + "storage.ini" + }; public MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 499bcb4063..416d2082c3 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -11,19 +11,11 @@ using osu.Game.Configuration; namespace osu.Game.IO { - public class OsuStorage : WrappedStorage + public class OsuStorage : MigratableStorage { private readonly GameHost host; private readonly StorageConfigManager storageConfig; - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - - internal static readonly string[] IGNORE_FILES = - { - "framework.ini", - "storage.ini" - }; - public OsuStorage(GameHost host) : base(host.Storage, string.Empty) { @@ -76,73 +68,5 @@ namespace osu.Game.IO deleteRecursive(source); } - - private static void deleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) - { - foreach (System.IO.FileInfo fi in target.GetFiles()) - { - if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) - continue; - - attemptOperation(() => fi.Delete()); - } - - foreach (DirectoryInfo dir in target.GetDirectories()) - { - if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) - continue; - - attemptOperation(() => dir.Delete(true)); - } - - if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - attemptOperation(target.Delete); - } - - private static void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) - { - // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo - Directory.CreateDirectory(destination.FullName); - - foreach (System.IO.FileInfo fi in source.GetFiles()) - { - if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) - continue; - - attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); - } - - foreach (DirectoryInfo dir in source.GetDirectories()) - { - if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) - continue; - - copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); - } - } - - /// - /// Attempt an IO operation multiple times and only throw if none of the attempts succeed. - /// - /// The action to perform. - /// The number of attempts (250ms wait between each). - private static void attemptOperation(Action action, int attempts = 10) - { - while (true) - { - try - { - action(); - return; - } - catch (Exception) - { - if (attempts-- == 0) - throw; - } - - Thread.Sleep(250); - } - } } } From f878388d578a798698fb7de98935f3fe27239274 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 11:56:14 +0200 Subject: [PATCH 034/350] Fix TestMigrationToSeeminglyNestedTarget failing --- osu.Game.Tournament/IO/TournamentStorage.cs | 3 --- osu.Game/IO/MigratableStorage.cs | 2 +- osu.Game/IO/OsuStorage.cs | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 1731b96095..c1629f270f 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -52,9 +52,6 @@ namespace osu.Game.Tournament.IO var destination = new DirectoryInfo(GetFullPath(default_tournament)); var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); - // if (!destination.Exists) - // destination.Create(); - if (source.Exists) { Logger.Log("Migrating tournament assets to default tournament storage."); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 004aa4e09a..95721a736e 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -53,7 +53,7 @@ namespace osu.Game.IO { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo if (!destination.Exists) - Directory.CreateDirectory(destination.FullName); + Directory.CreateDirectory(destination.FullName); foreach (System.IO.FileInfo fi in source.GetFiles()) { diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 416d2082c3..d37336234a 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -55,8 +55,6 @@ namespace osu.Game.IO { if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation)); - - deleteRecursive(destination); } copyRecursive(source, destination); From eec1e9ef4d660acf88de42ec6a814c96e2cf97cf Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:22:59 +0200 Subject: [PATCH 035/350] Remove unnecessary comments and added file check for tournament.ini on test start --- .../NonVisual/CustomTourneyDirectoryTest.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 37f456ae96..92ff39c67c 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -78,10 +78,13 @@ namespace osu.Game.Tournament.Tests.NonVisual { using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) { - // Recreate the old setup that uses "tournament" as the base path. string osuRoot = basePath(nameof(TestMigration)); + string configFile = Path.Combine(osuRoot, "tournament.ini"); - // Define all the paths for the old scenario + if (File.Exists(configFile)) + File.Delete(configFile); + + // Recreate the old setup that uses "tournament" as the base path. string oldPath = Path.Combine(osuRoot, "tournament"); string videosPath = Path.Combine(oldPath, "videos"); string modsPath = Path.Combine(oldPath, "mods"); @@ -141,7 +144,6 @@ namespace osu.Game.Tournament.Tests.NonVisual } finally { - // Cleaning up after ourselves. host.Storage.Delete("tournament.ini"); host.Storage.DeleteDirectory("tournaments"); host.Exit(); From 08759da3a7642647d08317231da389b7f78daa44 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:41:43 +0200 Subject: [PATCH 036/350] Move drawings.ini out of config subfolder --- .../NonVisual/CustomTourneyDirectoryTest.cs | 2 +- osu.Game.Tournament/IO/TournamentStorage.cs | 9 +-------- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 92ff39c67c..9e6675e09f 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tournament.Tests.NonVisual Assert.True(storage.Exists("drawings.txt")); Assert.True(storage.Exists("drawings_results.txt")); - Assert.True(storage.ConfigurationStorage.Exists("drawings.ini")); + Assert.True(storage.Exists("drawings.ini")); Assert.True(storage.Exists(videoFile)); Assert.True(storage.Exists(modFile)); diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index c1629f270f..c9d7ef3126 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,9 +15,7 @@ namespace osu.Game.Tournament.IO { private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; - internal readonly Storage ConfigurationStorage; private const string default_tournament = "default"; - private const string config_directory = "config"; public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) @@ -40,8 +38,6 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } - ConfigurationStorage = UnderlyingStorage.GetStorageForDirectory(config_directory); - VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } @@ -50,7 +46,6 @@ namespace osu.Game.Tournament.IO { var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_tournament)); - var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); if (source.Exists) { @@ -59,13 +54,11 @@ namespace osu.Game.Tournament.IO deleteRecursive(source); } - if (!cfgDestination.Exists) - destination.CreateSubdirectory(config_directory); moveFileIfExists("bracket.json", destination); moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); - moveFileIfExists("drawings.ini", cfgDestination); + moveFileIfExists("drawings.ini", destination); } private void moveFileIfExists(string file, DirectoryInfo destination) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index de909af152..8b6bd21ee6 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tournament.Screens.Drawings return; } - drawingsConfig = new DrawingsConfigManager(storage.ConfigurationStorage); + drawingsConfig = new DrawingsConfigManager(storage); InternalChildren = new Drawable[] { From 6b14079c0a80bc256f49376d939a861c7cd59ba7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:43:01 +0200 Subject: [PATCH 037/350] InspectCode changes --- osu.Game.Tournament/IO/TournamentStorage.cs | 9 +++------ osu.Game/IO/MigratableStorage.cs | 21 +++++++++------------ osu.Game/IO/OsuStorage.cs | 6 ++---- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index c9d7ef3126..12dcc2195c 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -1,8 +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; -using System.Threading; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IO; @@ -50,11 +48,10 @@ namespace osu.Game.Tournament.IO if (source.Exists) { Logger.Log("Migrating tournament assets to default tournament storage."); - copyRecursive(source, destination); - deleteRecursive(source); + CopyRecursive(source, destination); + DeleteRecursive(source); } - moveFileIfExists("bracket.json", destination); moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); @@ -67,7 +64,7 @@ namespace osu.Game.Tournament.IO { Logger.Log($"Migrating {file} to default tournament storage."); var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); - attemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); + AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); fileInfo.Delete(); } } diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 95721a736e..7efc37990f 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -5,15 +5,12 @@ using System; using System.IO; using System.Linq; using System.Threading; -using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Configuration; namespace osu.Game.IO { public abstract class MigratableStorage : WrappedStorage { - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; internal static readonly string[] IGNORE_FILES = @@ -22,19 +19,19 @@ namespace osu.Game.IO "storage.ini" }; - public MigratableStorage(Storage storage, string subPath = null) + protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { } - protected void deleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) + protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { foreach (System.IO.FileInfo fi in target.GetFiles()) { if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) continue; - attemptOperation(() => fi.Delete()); + AttemptOperation(() => fi.Delete()); } foreach (DirectoryInfo dir in target.GetDirectories()) @@ -42,14 +39,14 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) continue; - attemptOperation(() => dir.Delete(true)); + AttemptOperation(() => dir.Delete(true)); } if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - attemptOperation(target.Delete); + AttemptOperation(target.Delete); } - protected void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo if (!destination.Exists) @@ -60,7 +57,7 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) continue; - attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); + AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); } foreach (DirectoryInfo dir in source.GetDirectories()) @@ -68,7 +65,7 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) continue; - copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); } } @@ -77,7 +74,7 @@ namespace osu.Game.IO /// /// The action to perform. /// The number of attempts (250ms wait between each). - protected static void attemptOperation(Action action, int attempts = 10) + protected static void AttemptOperation(Action action, int attempts = 10) { while (true) { diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index d37336234a..3d224841f3 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -3,8 +3,6 @@ using System; using System.IO; -using System.Linq; -using System.Threading; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; @@ -57,14 +55,14 @@ namespace osu.Game.IO throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation)); } - copyRecursive(source, destination); + CopyRecursive(source, destination); ChangeTargetStorage(host.GetStorage(newLocation)); storageConfig.Set(StorageConfig.FullPath, newLocation); storageConfig.Save(); - deleteRecursive(source); + DeleteRecursive(source); } } } From a94dcc4923023d1322af32ed35ccb05f4ccfec57 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:59:38 +0200 Subject: [PATCH 038/350] Add xmldoc to MigratableStorage --- osu.Game/IO/MigratableStorage.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 7efc37990f..45aba41315 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -9,6 +9,9 @@ using osu.Framework.Platform; namespace osu.Game.IO { + /// + /// A that is migratable to different locations. + /// public abstract class MigratableStorage : WrappedStorage { internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; From e0d5a9182e76eb44d9cf18ac6b2ba259316fbb9b Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:59:56 +0200 Subject: [PATCH 039/350] make tournament migration private --- osu.Game.Tournament/IO/TournamentStorage.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 12dcc2195c..ebd8d2b63f 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.IO } else { - Migrate(); + migrate(); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); @@ -40,7 +40,7 @@ namespace osu.Game.Tournament.IO Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - internal void Migrate() + private void migrate() { var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_tournament)); From a899c754f11942c8967ff78139ef7bb5433e1b8c Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 13:03:24 +0200 Subject: [PATCH 040/350] Remove whitespace at the end of xmldoc line --- osu.Game/IO/MigratableStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 45aba41315..c4dc4bcfb2 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -10,7 +10,7 @@ using osu.Framework.Platform; namespace osu.Game.IO { /// - /// A that is migratable to different locations. + /// A that is migratable to different locations. /// public abstract class MigratableStorage : WrappedStorage { From a47d34f1db3ef3f69be44b0194630b06ebefda84 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 23 Jun 2020 23:34:26 +0200 Subject: [PATCH 041/350] make ignore properties protected virtual get-only in base --- .../NonVisual/CustomDataDirectoryTest.cs | 5 +++-- osu.Game/IO/MigratableStorage.cs | 11 +++------- osu.Game/IO/OsuStorage.cs | 20 +++++++++++++++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 5abefe3198..5278837073 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -126,6 +126,7 @@ namespace osu.Game.Tests.NonVisual { var osu = loadOsu(host); var storage = osu.Dependencies.Get(); + var osuStorage = storage as OsuStorage; // ensure we perform a save host.Dependencies.Get().Save(); @@ -151,13 +152,13 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in MigratableStorage.IGNORE_FILES) + foreach (var file in osuStorage.IGNORE_FILES) { Assert.That(host.Storage.Exists(file), Is.True); Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in MigratableStorage.IGNORE_DIRECTORIES) + foreach (var dir in osuStorage.IGNORE_DIRECTORIES) { Assert.That(host.Storage.ExistsDirectory(dir), Is.True); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index c4dc4bcfb2..0656e61f10 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,14 +14,9 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - - internal static readonly string[] IGNORE_FILES = - { - "framework.ini", - "storage.ini" - }; - + internal virtual string[] IGNORE_DIRECTORIES { get; } + internal virtual string[] IGNORE_FILES { get; } + protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 3d224841f3..bbec6eb575 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -14,6 +14,26 @@ namespace osu.Game.IO private readonly GameHost host; private readonly StorageConfigManager storageConfig; + internal override string[] IGNORE_DIRECTORIES + { + get + { + return new string[] { "cache" }; + } + } + + internal override string[] IGNORE_FILES + { + get + { + return new string[] + { + "framework.ini", + "storage.ini" + }; + } + } + public OsuStorage(GameHost host) : base(host.Storage, string.Empty) { From 8b9cf6fc52e84ba1cd0f3ceac2c9561f3e6d0c3c Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 23 Jun 2020 23:57:58 +0200 Subject: [PATCH 042/350] Remove default value in Storagemgr --- .../Configuration/TournamentStorageManager.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs index 653ea14352..e3d0a9e75c 100644 --- a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs +++ b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs @@ -14,12 +14,6 @@ namespace osu.Game.Tournament.Configuration : base(storage) { } - - protected override void InitialiseDefaults() - { - base.InitialiseDefaults(); - Set(StorageConfig.CurrentTournament, string.Empty); - } } public enum StorageConfig From 8e8458ab8fa082a7956265098a6a539f4be09244 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 23 Jun 2020 23:58:28 +0200 Subject: [PATCH 043/350] make migrate public abstract in base and override --- osu.Game.Tournament/IO/TournamentStorage.cs | 8 ++++---- osu.Game/IO/MigratableStorage.cs | 4 +++- osu.Game/IO/OsuStorage.cs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index ebd8d2b63f..5c1d9a39c5 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tournament.IO private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; private const string default_tournament = "default"; - + public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.IO } else { - migrate(); + Migrate(GetFullPath(default_tournament)); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); @@ -40,10 +40,10 @@ namespace osu.Game.Tournament.IO Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - private void migrate() + override public void Migrate(string newLocation) { var source = new DirectoryInfo(storage.GetFullPath("tournament")); - var destination = new DirectoryInfo(GetFullPath(default_tournament)); + var destination = new DirectoryInfo(newLocation); if (source.Exists) { diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 0656e61f10..0f064dfe2d 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -16,12 +16,14 @@ namespace osu.Game.IO { internal virtual string[] IGNORE_DIRECTORIES { get; } internal virtual string[] IGNORE_FILES { get; } - + protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { } + abstract public void Migrate(string newLocation); + protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { foreach (System.IO.FileInfo fi in target.GetFiles()) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index bbec6eb575..8890ecf843 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -53,7 +53,7 @@ namespace osu.Game.IO Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); } - public void Migrate(string newLocation) + override public void Migrate(string newLocation) { var source = new DirectoryInfo(GetFullPath(".")); var destination = new DirectoryInfo(newLocation); From 7a3315dcf82b5aea1ce12343ca81d9da95b6176f Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:00:21 +0200 Subject: [PATCH 044/350] invert and early return --- osu.Game.Tournament/IO/TournamentStorage.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 5c1d9a39c5..5f90598890 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tournament.IO private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; private const string default_tournament = "default"; - + public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { @@ -60,13 +60,13 @@ namespace osu.Game.Tournament.IO private void moveFileIfExists(string file, DirectoryInfo destination) { - if (storage.Exists(file)) - { - Logger.Log($"Migrating {file} to default tournament storage."); - var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); - AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); - fileInfo.Delete(); - } + if (!storage.Exists(file)) + return; + + Logger.Log($"Migrating {file} to default tournament storage."); + var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); + AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); + fileInfo.Delete(); } } } From 0ca8c961c8148813726a1172cc680b6d10a0fffb Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:04:57 +0200 Subject: [PATCH 045/350] Remove string interpolation & unnecessary test setup --- .../NonVisual/CustomTourneyDirectoryTest.cs | 5 ----- osu.Game.Tournament/Components/TourneyVideo.cs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 9e6675e09f..29e1725c6d 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -18,11 +18,6 @@ namespace osu.Game.Tournament.Tests.NonVisual [TestFixture] public class CustomTourneyDirectoryTest { - [SetUp] - public void SetUp() - { - } - [Test] public void TestDefaultDirectory() { diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 5a595f4f44..0052b9a431 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Components [BackgroundDependencyLoader] private void load(TournamentStorage storage) { - var stream = storage.VideoStore.GetStream($@"{filename}"); + var stream = storage.VideoStore.GetStream(filename); if (stream != null) { From e5851be9ad222094f0d710c79ac8d5d8567d439d Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:06:27 +0200 Subject: [PATCH 046/350] change accessor from internal readonly to public get-only Also changes the class accessor from internal to public --- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- osu.Game.Tournament/IO/TournamentVideoResourceStore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 5f90598890..14ff8d59e5 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.IO public class TournamentStorage : MigratableStorage { private readonly Storage storage; - internal readonly TournamentVideoResourceStore VideoStore; + public TournamentVideoResourceStore VideoStore { get; } private const string default_tournament = "default"; public TournamentStorage(Storage storage) diff --git a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs index 1ccd20fe21..4b26840b79 100644 --- a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs +++ b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs @@ -6,7 +6,7 @@ using osu.Framework.Platform; namespace osu.Game.Tournament.IO { - internal class TournamentVideoResourceStore : NamespacedResourceStore + public class TournamentVideoResourceStore : NamespacedResourceStore { public TournamentVideoResourceStore(Storage storage) : base(new StorageBackedResourceStore(storage), "videos") From 9d2392b6b1c8fccd9e15625a72cabf21419c6028 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:14:44 +0200 Subject: [PATCH 047/350] Cache TournamentStorage as Storage and only cast when necessary --- osu.Game.Tournament/Components/TourneyVideo.cs | 7 ++++--- .../Screens/Drawings/Components/StorageBackedTeamList.cs | 6 +++--- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 6 +++--- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 0052b9a431..17d4eb7a28 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; +using osu.Framework.Platform; using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Tournament.IO; @@ -18,7 +19,6 @@ namespace osu.Game.Tournament.Components private readonly string filename; private readonly bool drawFallbackGradient; private Video video; - private ManualClock manualClock; public TourneyVideo(string filename, bool drawFallbackGradient = false) @@ -28,9 +28,10 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(TournamentStorage storage) + private void load(Storage storage) { - var stream = storage.VideoStore.GetStream(filename); + var tournamentStorage = storage as TournamentStorage; + var stream = tournamentStorage.VideoStore.GetStream(filename); if (stream != null) { diff --git a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index ecc23181be..f96ec01cbb 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using osu.Framework.Logging; -using osu.Game.Tournament.IO; +using osu.Framework.Platform; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Screens.Drawings.Components @@ -14,9 +14,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { private const string teams_filename = "drawings.txt"; - private readonly TournamentStorage storage; + private readonly Storage storage; - public StorageBackedTeamList(TournamentStorage storage) + public StorageBackedTeamList(Storage storage) { this.storage = storage; } diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 8b6bd21ee6..e10154b722 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -12,9 +12,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Tournament.Components; -using osu.Game.Tournament.IO; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Drawings.Components; using osuTK; @@ -36,12 +36,12 @@ namespace osu.Game.Tournament.Screens.Drawings private Task writeOp; - private TournamentStorage storage; + private Storage storage; public ITeamList TeamList; [BackgroundDependencyLoader] - private void load(TextureStore textures, TournamentStorage storage) + private void load(TextureStore textures, Storage storage) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 0702b435a5..6a533f96d8 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); + dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); Textures.AddStore(new TextureLoaderStore(storage.VideoStore)); From c32ef5e718c4a7df5908ea8dcbce9d998f3a1926 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:37:29 +0200 Subject: [PATCH 048/350] Address formatting issues --- .../NonVisual/CustomDataDirectoryTest.cs | 4 ++-- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- osu.Game/IO/MigratableStorage.cs | 14 +++++------ osu.Game/IO/OsuStorage.cs | 24 +++++-------------- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 5278837073..125d2b3ef7 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -152,13 +152,13 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in osuStorage.IGNORE_FILES) + foreach (var file in osuStorage.IgnoreFiles) { Assert.That(host.Storage.Exists(file), Is.True); Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in osuStorage.IGNORE_DIRECTORIES) + foreach (var dir in osuStorage.IgnoreDirectories) { Assert.That(host.Storage.ExistsDirectory(dir), Is.True); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 14ff8d59e5..b906ea6c50 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tournament.IO Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - override public void Migrate(string newLocation) + public override void Migrate(string newLocation) { var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(newLocation); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 0f064dfe2d..41a057d016 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,21 +14,21 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - internal virtual string[] IGNORE_DIRECTORIES { get; } - internal virtual string[] IGNORE_FILES { get; } + internal virtual string[] IgnoreDirectories => new string[] { }; + internal virtual string[] IgnoreFiles => new string[] { }; protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { } - abstract public void Migrate(string newLocation); + public abstract void Migrate(string newLocation); protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { foreach (System.IO.FileInfo fi in target.GetFiles()) { - if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) + if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; AttemptOperation(() => fi.Delete()); @@ -36,7 +36,7 @@ namespace osu.Game.IO foreach (DirectoryInfo dir in target.GetDirectories()) { - if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) + if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; AttemptOperation(() => dir.Delete(true)); @@ -54,7 +54,7 @@ namespace osu.Game.IO foreach (System.IO.FileInfo fi in source.GetFiles()) { - if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) + if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); @@ -62,7 +62,7 @@ namespace osu.Game.IO foreach (DirectoryInfo dir in source.GetDirectories()) { - if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) + if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 8890ecf843..514f172f74 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -14,25 +14,13 @@ namespace osu.Game.IO private readonly GameHost host; private readonly StorageConfigManager storageConfig; - internal override string[] IGNORE_DIRECTORIES - { - get - { - return new string[] { "cache" }; - } - } + internal override string[] IgnoreDirectories => new[] { "cache" }; - internal override string[] IGNORE_FILES + internal override string[] IgnoreFiles => new[] { - get - { - return new string[] - { - "framework.ini", - "storage.ini" - }; - } - } + "framework.ini", + "storage.ini" + }; public OsuStorage(GameHost host) : base(host.Storage, string.Empty) @@ -53,7 +41,7 @@ namespace osu.Game.IO Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); } - override public void Migrate(string newLocation) + public override void Migrate(string newLocation) { var source = new DirectoryInfo(GetFullPath(".")); var destination = new DirectoryInfo(newLocation); From af1134084948db534072997b41ee7a7e5758c2b0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 02:13:28 +0200 Subject: [PATCH 049/350] Fix nullref exceptions and redundant explicit type --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 4 ++-- osu.Game.Tournament/Components/TourneyVideo.cs | 3 +-- osu.Game/IO/MigratableStorage.cs | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 125d2b3ef7..c8a5988104 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -152,13 +152,13 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in osuStorage.IgnoreFiles) + foreach (var file in osuStorage?.IgnoreFiles ?? Array.Empty()) { Assert.That(host.Storage.Exists(file), Is.True); Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in osuStorage.IgnoreDirectories) + foreach (var dir in osuStorage?.IgnoreDirectories ?? Array.Empty()) { Assert.That(host.Storage.ExistsDirectory(dir), Is.True); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 17d4eb7a28..794b72b3a9 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -30,8 +30,7 @@ namespace osu.Game.Tournament.Components [BackgroundDependencyLoader] private void load(Storage storage) { - var tournamentStorage = storage as TournamentStorage; - var stream = tournamentStorage.VideoStore.GetStream(filename); + var stream = (storage as TournamentStorage)?.VideoStore.GetStream(filename); if (stream != null) { diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 41a057d016..ec85e0bac9 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,8 +14,8 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - internal virtual string[] IgnoreDirectories => new string[] { }; - internal virtual string[] IgnoreFiles => new string[] { }; + internal virtual string[] IgnoreDirectories => Array.Empty(); + internal virtual string[] IgnoreFiles => Array.Empty(); protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) From 839f197111c55d00474da01aced1a07e46a7fe69 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 02:37:59 +0200 Subject: [PATCH 050/350] Change type from TournamentStorage to Storage in tests --- .../NonVisual/CustomTourneyDirectoryTest.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 29e1725c6d..4cede15d7a 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -10,7 +10,6 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Game.Tournament.Configuration; -using osu.Game.Tournament.IO; using osu.Game.Tests; namespace osu.Game.Tournament.Tests.NonVisual @@ -26,7 +25,7 @@ namespace osu.Game.Tournament.Tests.NonVisual try { var osu = loadOsu(host); - var storage = osu.Dependencies.Get(); + var storage = osu.Dependencies.Get(); var defaultStorage = Path.Combine(tournamentBasePath(nameof(TestDefaultDirectory)), "default"); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorage)); } @@ -57,7 +56,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = loadOsu(host); - storage = osu.Dependencies.Get(); + storage = osu.Dependencies.Get(); Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), "custom"))); } @@ -113,7 +112,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = loadOsu(host); - var storage = osu.Dependencies.Get(); + var storage = osu.Dependencies.Get(); var migratedPath = Path.Combine(tournamentBasePath(nameof(TestMigration)), "default"); From c94f95cc0d6bf3ba92098d6fe5f3190d8ddf4153 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 02:40:22 +0200 Subject: [PATCH 051/350] Check if the file exists before reading This is (also) to address the review from bdach about StorageManager initialising a default value that gets overwritten upon migration anyway. --- osu.Game.Tournament/IO/TournamentStorage.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index b906ea6c50..ed1bfb7449 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -22,11 +22,9 @@ namespace osu.Game.Tournament.IO TournamentStorageManager storageConfig = new TournamentStorageManager(storage); - var currentTournament = storageConfig.Get(StorageConfig.CurrentTournament); - - if (!string.IsNullOrEmpty(currentTournament)) + if (storage.Exists("tournament.ini")) { - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(currentTournament)); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(storageConfig.Get(StorageConfig.CurrentTournament))); } else { From 063503f4db9ec775ffaa1204fc2e69a55d25329c Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 20:43:56 +0200 Subject: [PATCH 052/350] Move null check outside of the loop --- .../NonVisual/CustomDataDirectoryTest.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index c8a5988104..4149e3d3ef 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -152,16 +152,19 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in osuStorage?.IgnoreFiles ?? Array.Empty()) + if (osuStorage != null) { - Assert.That(host.Storage.Exists(file), Is.True); - Assert.That(storage.Exists(file), Is.False); - } + foreach (var file in osuStorage.IgnoreFiles) + { + Assert.That(host.Storage.Exists(file), Is.True); + Assert.That(storage.Exists(file), Is.False); + } - foreach (var dir in osuStorage?.IgnoreDirectories ?? Array.Empty()) - { - Assert.That(host.Storage.ExistsDirectory(dir), Is.True); - Assert.That(storage.ExistsDirectory(dir), Is.False); + foreach (var dir in osuStorage.IgnoreDirectories) + { + Assert.That(host.Storage.ExistsDirectory(dir), Is.True); + Assert.That(storage.ExistsDirectory(dir), Is.False); + } } Assert.That(new StreamReader(host.Storage.GetStream("storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); From 47a732ef604ac2f675ef71683d69eb10930d4785 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 23:01:56 +0200 Subject: [PATCH 053/350] Address review comments Now asserting instead of an if-statement, change cast from OsuStorage to MigratableStorage and make internal virtual properties protected. --- .../NonVisual/CustomDataDirectoryTest.cs | 25 +++++++++---------- osu.Game/IO/MigratableStorage.cs | 4 +-- osu.Game/IO/OsuStorage.cs | 4 +-- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 4149e3d3ef..43c1c77786 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.NonVisual { var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - var osuStorage = storage as OsuStorage; + var osuStorage = storage as MigratableStorage; // ensure we perform a save host.Dependencies.Get().Save(); @@ -152,19 +152,18 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - if (osuStorage != null) - { - foreach (var file in osuStorage.IgnoreFiles) - { - Assert.That(host.Storage.Exists(file), Is.True); - Assert.That(storage.Exists(file), Is.False); - } + Assert.That(osuStorage, Is.Not.Null); - foreach (var dir in osuStorage.IgnoreDirectories) - { - Assert.That(host.Storage.ExistsDirectory(dir), Is.True); - Assert.That(storage.ExistsDirectory(dir), Is.False); - } + foreach (var file in osuStorage.IgnoreFiles) + { + Assert.That(host.Storage.Exists(file), Is.True); + Assert.That(storage.Exists(file), Is.False); + } + + foreach (var dir in osuStorage.IgnoreDirectories) + { + Assert.That(host.Storage.ExistsDirectory(dir), Is.True); + Assert.That(storage.ExistsDirectory(dir), Is.False); } Assert.That(new StreamReader(host.Storage.GetStream("storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index ec85e0bac9..faa39d2ef8 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,8 +14,8 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - internal virtual string[] IgnoreDirectories => Array.Empty(); - internal virtual string[] IgnoreFiles => Array.Empty(); + public virtual string[] IgnoreDirectories => Array.Empty(); + public virtual string[] IgnoreFiles => Array.Empty(); protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 514f172f74..31ee802141 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -14,9 +14,9 @@ namespace osu.Game.IO private readonly GameHost host; private readonly StorageConfigManager storageConfig; - internal override string[] IgnoreDirectories => new[] { "cache" }; + public override string[] IgnoreDirectories => new[] { "cache" }; - internal override string[] IgnoreFiles => new[] + public override string[] IgnoreFiles => new[] { "framework.ini", "storage.ini" From d82d901542ddb0d4018caf6b89ddc64d307abd8d Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 25 Jun 2020 20:36:55 +0200 Subject: [PATCH 054/350] Reuse custom_tournament where it was still used as a literal --- .../NonVisual/CustomTourneyDirectoryTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 4cede15d7a..ce0ceae2e1 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Tests.NonVisual storage = osu.Dependencies.Get(); - Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), "custom"))); + Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), custom_tournament))); } finally { @@ -80,6 +80,7 @@ namespace osu.Game.Tournament.Tests.NonVisual // Recreate the old setup that uses "tournament" as the base path. string oldPath = Path.Combine(osuRoot, "tournament"); + string videosPath = Path.Combine(oldPath, "videos"); string modsPath = Path.Combine(oldPath, "mods"); string flagsPath = Path.Combine(oldPath, "flags"); From 0cddb85f1b83eb1c2b7a4dab43ec17b2f4e35cee Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 28 Jun 2020 15:27:50 +0200 Subject: [PATCH 055/350] Move storageconfig set and saving to migrate method --- .../NonVisual/CustomTourneyDirectoryTest.cs | 2 +- osu.Game.Tournament/IO/TournamentStorage.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index ce0ceae2e1..b75a9a6929 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tournament.Tests.NonVisual // Recreate the old setup that uses "tournament" as the base path. string oldPath = Path.Combine(osuRoot, "tournament"); - + string videosPath = Path.Combine(oldPath, "videos"); string modsPath = Path.Combine(oldPath, "mods"); string flagsPath = Path.Combine(oldPath, "flags"); diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index ed1bfb7449..ddc298a7ea 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -11,16 +11,17 @@ namespace osu.Game.Tournament.IO { public class TournamentStorage : MigratableStorage { - private readonly Storage storage; - public TournamentVideoResourceStore VideoStore { get; } private const string default_tournament = "default"; + private readonly Storage storage; + private readonly TournamentStorageManager storageConfig; + public TournamentVideoResourceStore VideoStore { get; } public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { this.storage = storage; - TournamentStorageManager storageConfig = new TournamentStorageManager(storage); + storageConfig = new TournamentStorageManager(storage); if (storage.Exists("tournament.ini")) { @@ -29,8 +30,6 @@ namespace osu.Game.Tournament.IO else { Migrate(GetFullPath(default_tournament)); - storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); - storageConfig.Save(); ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } @@ -54,6 +53,8 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", destination); + storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); + storageConfig.Save(); } private void moveFileIfExists(string file, DirectoryInfo destination) From c3cd2a74f5f0ee89e531d564db3d7a5cb9e3ed04 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 1 Jul 2020 22:57:16 +0200 Subject: [PATCH 056/350] Move general purpose migration to MigratableStorage --- osu.Game.Tournament/IO/TournamentStorage.cs | 10 +++--- osu.Game/IO/MigratableStorage.cs | 31 +++++++++++++++++- osu.Game/IO/OsuStorage.cs | 36 ++------------------- osu.Game/OsuGameBase.cs | 2 +- 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index ddc298a7ea..6505135b42 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -28,19 +28,16 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(storageConfig.Get(StorageConfig.CurrentTournament))); } else - { - Migrate(GetFullPath(default_tournament)); - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); - } + Migrate(UnderlyingStorage.GetStorageForDirectory(default_tournament)); VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - public override void Migrate(string newLocation) + public override void Migrate(Storage newStorage) { var source = new DirectoryInfo(storage.GetFullPath("tournament")); - var destination = new DirectoryInfo(newLocation); + var destination = new DirectoryInfo(newStorage.GetFullPath(".")); if (source.Exists) { @@ -53,6 +50,7 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", destination); + ChangeTargetStorage(newStorage); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); } diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index faa39d2ef8..13aae92dfd 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -22,7 +22,36 @@ namespace osu.Game.IO { } - public abstract void Migrate(string newLocation); + /// + /// A general purpose migration method to move the storage to a different location. + /// The target storage of the migration. + /// + public virtual void Migrate(Storage newStorage) + { + var source = new DirectoryInfo(GetFullPath(".")); + var destination = new DirectoryInfo(newStorage.GetFullPath(".")); + + // using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620) + var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar); + var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar); + + if (sourceUri == destinationUri) + throw new ArgumentException("Destination provided is already the current location", nameof(newStorage)); + + if (sourceUri.IsBaseOf(destinationUri)) + throw new ArgumentException("Destination provided is inside the source", nameof(newStorage)); + + // ensure the new location has no files present, else hard abort + if (destination.Exists) + { + if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) + throw new ArgumentException("Destination provided already has files or directories present", nameof(newStorage)); + } + + CopyRecursive(source, destination); + ChangeTargetStorage(newStorage); + DeleteRecursive(source); + } protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 31ee802141..7104031b56 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -1,8 +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; -using System.IO; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; @@ -11,7 +9,6 @@ namespace osu.Game.IO { public class OsuStorage : MigratableStorage { - private readonly GameHost host; private readonly StorageConfigManager storageConfig; public override string[] IgnoreDirectories => new[] { "cache" }; @@ -25,8 +22,6 @@ namespace osu.Game.IO public OsuStorage(GameHost host) : base(host.Storage, string.Empty) { - this.host = host; - storageConfig = new StorageConfigManager(host.Storage); var customStoragePath = storageConfig.Get(StorageConfig.FullPath); @@ -41,36 +36,11 @@ namespace osu.Game.IO Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); } - public override void Migrate(string newLocation) + public override void Migrate(Storage newStorage) { - var source = new DirectoryInfo(GetFullPath(".")); - var destination = new DirectoryInfo(newLocation); - - // using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620) - var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar); - var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar); - - if (sourceUri == destinationUri) - throw new ArgumentException("Destination provided is already the current location", nameof(newLocation)); - - if (sourceUri.IsBaseOf(destinationUri)) - throw new ArgumentException("Destination provided is inside the source", nameof(newLocation)); - - // ensure the new location has no files present, else hard abort - if (destination.Exists) - { - if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) - throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation)); - } - - CopyRecursive(source, destination); - - ChangeTargetStorage(host.GetStorage(newLocation)); - - storageConfig.Set(StorageConfig.FullPath, newLocation); + base.Migrate(newStorage); + storageConfig.Set(StorageConfig.FullPath, newStorage.GetFullPath(".")); storageConfig.Save(); - - DeleteRecursive(source); } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3e7311092e..97a4e212e8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -370,7 +370,7 @@ namespace osu.Game public void Migrate(string path) { contextFactory.FlushConnections(); - (Storage as OsuStorage)?.Migrate(path); + (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); } } } From 66e61aacff983d7354e6bb267cd472ee090d49fe Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 2 Jul 2020 00:32:09 +0200 Subject: [PATCH 057/350] Logger now shows the actual path of the destination Forgot to change this while changing the param from string to Storage --- osu.Game/IO/MigratableStorage.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 13aae92dfd..21087d7dc6 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -36,16 +36,16 @@ namespace osu.Game.IO var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar); if (sourceUri == destinationUri) - throw new ArgumentException("Destination provided is already the current location", nameof(newStorage)); + throw new ArgumentException("Destination provided is already the current location", destination.FullName); if (sourceUri.IsBaseOf(destinationUri)) - throw new ArgumentException("Destination provided is inside the source", nameof(newStorage)); + throw new ArgumentException("Destination provided is inside the source", destination.FullName); // ensure the new location has no files present, else hard abort if (destination.Exists) { if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) - throw new ArgumentException("Destination provided already has files or directories present", nameof(newStorage)); + throw new ArgumentException("Destination provided already has files or directories present", destination.FullName); } CopyRecursive(source, destination); From 321815f535b53fce4f4db3749800422d55ef8ba7 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+FlashyReese@users.noreply.github.com> Date: Thu, 9 Jul 2020 14:01:28 -0500 Subject: [PATCH 058/350] Add playfield bounds box with toggle and dim slider --- osu.Game/Configuration/OsuConfigManager.cs | 7 +++- .../Sections/Gameplay/GeneralSettings.cs | 14 ++++++- osu.Game/Rulesets/UI/Playfield.cs | 41 ++++++++++++++++++- .../Play/PlayerSettings/VisualSettings.cs | 15 ++++++- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9d31bc9bba..40a132a8e8 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -99,6 +99,9 @@ namespace osu.Game.Configuration Set(OsuSetting.IncreaseFirstObjectVisibility, true); + Set(OsuSetting.ShowPlayfieldArea, false); + Set(OsuSetting.PlayfieldAreaDimLevel, 0.1, 0, 1, 0.01); + // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -227,6 +230,8 @@ namespace osu.Game.Configuration IntroSequence, UIHoldActivationDelay, HitLighting, - MenuBackgroundSource + MenuBackgroundSource, + ShowPlayfieldArea, + PlayfieldAreaDimLevel } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 93a02ea0e4..ad02b54dd8 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -76,7 +76,19 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Score display mode", Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) - } + }, + new SettingsCheckbox + { + LabelText = "Show playfield area", + Bindable = config.GetBindable(OsuSetting.ShowPlayfieldArea) + }, + new SettingsSlider + { + LabelText = "Playfield area dim", + Bindable = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel), + KeyboardStep = 0.01f, + DisplayAsPercentage = true + }, }; } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index c52183f3f2..2ec84cca8c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,6 +12,9 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods; using osuTK; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; +using osu.Game.Configuration; namespace osu.Game.Rulesets.UI { @@ -51,6 +54,10 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool DisplayJudgements = new BindableBool(true); + private Bindable showPlayfieldArea; + private Bindable playfieldAreaDimLevel; + private Box playfieldArea; + /// /// Creates a new . /// @@ -65,7 +72,7 @@ namespace osu.Game.Rulesets.UI private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { Cursor = CreateCursor(); @@ -76,6 +83,38 @@ namespace osu.Game.Rulesets.UI AddInternal(Cursor); } + + showPlayfieldArea = config.GetBindable(OsuSetting.ShowPlayfieldArea); + playfieldAreaDimLevel = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel); + showPlayfieldArea.ValueChanged += _ => UpdateVisuals(); + playfieldAreaDimLevel.ValueChanged += _ => UpdateVisuals(); + UpdateVisuals(); + } + protected virtual void UpdateVisuals() + { + if(playfieldArea == null) + { + if (showPlayfieldArea.Value) + { + AddInternal(playfieldArea = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = (float)playfieldAreaDimLevel.Value, + }); + } + } + else + { + if (showPlayfieldArea.Value) + { + playfieldArea.Alpha = (float)playfieldAreaDimLevel.Value; + } + else + { + playfieldArea.Alpha = 0; + } + } } /// diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index d6c66d0751..36e7c53132 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -14,9 +14,11 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; + private readonly PlayerSliderBar playfieldAreaDimSliderBar; private readonly PlayerCheckbox showStoryboardToggle; private readonly PlayerCheckbox beatmapSkinsToggle; private readonly PlayerCheckbox beatmapHitsoundsToggle; + private readonly PlayerCheckbox showPlayfieldAreaToggle; public VisualSettings() { @@ -39,12 +41,21 @@ namespace osu.Game.Screens.Play.PlayerSettings DisplayAsPercentage = true }, new OsuSpriteText + { + Text = "Playfieldd area dim:" + }, + playfieldAreaDimSliderBar = new PlayerSliderBar + { + DisplayAsPercentage = true + }, + new OsuSpriteText { Text = "Toggles:" }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" }, + showPlayfieldAreaToggle = new PlayerCheckbox { LabelText = "Show playfield area" } }; } @@ -53,9 +64,11 @@ namespace osu.Game.Screens.Play.PlayerSettings { dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); + playfieldAreaDimSliderBar.Bindable = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel); showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); + showPlayfieldAreaToggle.Current = config.GetBindable(OsuSetting.ShowPlayfieldArea); } } } From 8121ccaad077e5a93088185ea0f92036af7ca6a1 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+FlashyReese@users.noreply.github.com> Date: Thu, 9 Jul 2020 15:00:26 -0500 Subject: [PATCH 059/350] Change Box to EditorPlayfieldBorder --- osu.Game/Rulesets/UI/Playfield.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 2ec84cca8c..f6eb74a030 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,9 +12,8 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods; using osuTK; -using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; using osu.Game.Configuration; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.UI { @@ -56,7 +55,7 @@ namespace osu.Game.Rulesets.UI private Bindable showPlayfieldArea; private Bindable playfieldAreaDimLevel; - private Box playfieldArea; + private EditorPlayfieldBorder playfieldArea; /// /// Creates a new . @@ -90,16 +89,16 @@ namespace osu.Game.Rulesets.UI playfieldAreaDimLevel.ValueChanged += _ => UpdateVisuals(); UpdateVisuals(); } + protected virtual void UpdateVisuals() { - if(playfieldArea == null) + if (playfieldArea == null) { if (showPlayfieldArea.Value) { - AddInternal(playfieldArea = new Box + AddInternal(playfieldArea = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, Alpha = (float)playfieldAreaDimLevel.Value, }); } From 0d95b768aa2e04f8543731afa71a31282c4c42c3 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+FlashyReese@users.noreply.github.com> Date: Fri, 10 Jul 2020 07:34:48 -0500 Subject: [PATCH 060/350] Rename and Move EditorPlayfieldBorder to PlayfieldBorder for general purpose --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +- osu.Game/Rulesets/UI/Playfield.cs | 40 +------------------ ...rPlayfieldBorder.cs => PlayfieldBorder.cs} | 6 +-- 3 files changed, 6 insertions(+), 43 deletions(-) rename osu.Game/Screens/{Edit/Compose/Components/EditorPlayfieldBorder.cs => PlayfieldBorder.cs} (82%) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c25fb03fd0..6028ab77e1 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose; @@ -108,7 +109,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] { LayerBelowRuleset, - new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } + new PlayfieldBorder { RelativeSizeAxes = Axes.Both } }), drawableRulesetWrapper, // layers above playfield diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index f6eb74a030..c52183f3f2 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,8 +12,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods; using osuTK; -using osu.Game.Configuration; -using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.UI { @@ -53,10 +51,6 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool DisplayJudgements = new BindableBool(true); - private Bindable showPlayfieldArea; - private Bindable playfieldAreaDimLevel; - private EditorPlayfieldBorder playfieldArea; - /// /// Creates a new . /// @@ -71,7 +65,7 @@ namespace osu.Game.Rulesets.UI private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load() { Cursor = CreateCursor(); @@ -82,38 +76,6 @@ namespace osu.Game.Rulesets.UI AddInternal(Cursor); } - - showPlayfieldArea = config.GetBindable(OsuSetting.ShowPlayfieldArea); - playfieldAreaDimLevel = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel); - showPlayfieldArea.ValueChanged += _ => UpdateVisuals(); - playfieldAreaDimLevel.ValueChanged += _ => UpdateVisuals(); - UpdateVisuals(); - } - - protected virtual void UpdateVisuals() - { - if (playfieldArea == null) - { - if (showPlayfieldArea.Value) - { - AddInternal(playfieldArea = new EditorPlayfieldBorder - { - RelativeSizeAxes = Axes.Both, - Alpha = (float)playfieldAreaDimLevel.Value, - }); - } - } - else - { - if (showPlayfieldArea.Value) - { - playfieldArea.Alpha = (float)playfieldAreaDimLevel.Value; - } - else - { - playfieldArea.Alpha = 0; - } - } } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs b/osu.Game/Screens/PlayfieldBorder.cs similarity index 82% rename from osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs rename to osu.Game/Screens/PlayfieldBorder.cs index 4d956336b7..a3be38f0a2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs +++ b/osu.Game/Screens/PlayfieldBorder.cs @@ -6,14 +6,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Compose.Components +namespace osu.Game.Screens { /// /// Provides a border around the playfield. /// - public class EditorPlayfieldBorder : CompositeDrawable + public class PlayfieldBorder : CompositeDrawable { - public EditorPlayfieldBorder() + public PlayfieldBorder() { RelativeSizeAxes = Axes.Both; From d40f209f4bbb15406b5512ad77585311ead25af4 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+FlashyReese@users.noreply.github.com> Date: Fri, 10 Jul 2020 07:36:21 -0500 Subject: [PATCH 061/350] Move Playfield Border to OsuPlayfield Ruleset --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 19 +++++++++++++++++++ osu.Game/Configuration/OsuConfigManager.cs | 6 ++---- .../Sections/Gameplay/GeneralSettings.cs | 13 +++---------- .../Play/PlayerSettings/VisualSettings.cs | 15 +-------------- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 4b1a2ce43c..3189db69a5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -12,6 +12,10 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; +using osu.Framework.Allocation; +using osu.Game.Configuration; +using osu.Framework.Bindables; +using osu.Game.Screens; namespace osu.Game.Rulesets.Osu.UI { @@ -26,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); + private Bindable showPlayfieldBorder; + public OsuPlayfield() { InternalChildren = new Drawable[] @@ -56,6 +62,19 @@ namespace osu.Game.Rulesets.Osu.UI hitPolicy = new OrderedHitPolicy(HitObjectContainer); } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + showPlayfieldBorder = config.GetBindable(OsuSetting.ShowPlayfieldBorder); + if (showPlayfieldBorder.Value) + { + AddInternal(new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both + }); + } + } + public override void Add(DrawableHitObject h) { h.OnNewResult += onNewResult; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 40a132a8e8..9ed73b7bd6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -99,8 +99,7 @@ namespace osu.Game.Configuration Set(OsuSetting.IncreaseFirstObjectVisibility, true); - Set(OsuSetting.ShowPlayfieldArea, false); - Set(OsuSetting.PlayfieldAreaDimLevel, 0.1, 0, 1, 0.01); + Set(OsuSetting.ShowPlayfieldBorder, false); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -231,7 +230,6 @@ namespace osu.Game.Configuration UIHoldActivationDelay, HitLighting, MenuBackgroundSource, - ShowPlayfieldArea, - PlayfieldAreaDimLevel + ShowPlayfieldBorder } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index ad02b54dd8..85eb61edff 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -79,16 +79,9 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }, new SettingsCheckbox { - LabelText = "Show playfield area", - Bindable = config.GetBindable(OsuSetting.ShowPlayfieldArea) - }, - new SettingsSlider - { - LabelText = "Playfield area dim", - Bindable = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel), - KeyboardStep = 0.01f, - DisplayAsPercentage = true - }, + LabelText = "Show playfield border", + Bindable = config.GetBindable(OsuSetting.ShowPlayfieldBorder) + } }; } } diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index 36e7c53132..d6c66d0751 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -14,11 +14,9 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; - private readonly PlayerSliderBar playfieldAreaDimSliderBar; private readonly PlayerCheckbox showStoryboardToggle; private readonly PlayerCheckbox beatmapSkinsToggle; private readonly PlayerCheckbox beatmapHitsoundsToggle; - private readonly PlayerCheckbox showPlayfieldAreaToggle; public VisualSettings() { @@ -41,21 +39,12 @@ namespace osu.Game.Screens.Play.PlayerSettings DisplayAsPercentage = true }, new OsuSpriteText - { - Text = "Playfieldd area dim:" - }, - playfieldAreaDimSliderBar = new PlayerSliderBar - { - DisplayAsPercentage = true - }, - new OsuSpriteText { Text = "Toggles:" }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" }, - showPlayfieldAreaToggle = new PlayerCheckbox { LabelText = "Show playfield area" } + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; } @@ -64,11 +53,9 @@ namespace osu.Game.Screens.Play.PlayerSettings { dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); - playfieldAreaDimSliderBar.Bindable = config.GetBindable(OsuSetting.PlayfieldAreaDimLevel); showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); - showPlayfieldAreaToggle.Current = config.GetBindable(OsuSetting.ShowPlayfieldArea); } } } From 6a144fba80a4ec8b8c10367010d5c02f8129d7f6 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Mon, 20 Jul 2020 17:24:17 +0700 Subject: [PATCH 062/350] add epilepsy warning in metadata display --- .../Screens/Play/BeatmapMetadataDisplay.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index a84a85ea47..069ac69622 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -49,6 +49,37 @@ namespace osu.Game.Screens.Play } } + private class EpilepsyWarning : FillFlowContainer + { + public EpilepsyWarning() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Vertical; + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Icon = FontAwesome.Solid.ExclamationTriangle, + Size = new Vector2(40) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "This beatmap contains scenes with rapidly flashing colours." + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Please take caution if you are affected by epilepsy." + } + }; + } + } + private readonly WorkingBeatmap beatmap; private readonly Bindable> mods; private readonly Drawable facade; @@ -162,6 +193,12 @@ namespace osu.Game.Screens.Play AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 20 }, Current = mods + }, + new EpilepsyWarning + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 20 }, } }, } From acbf13ddc4050e3462f34ab4c3bf5075aff1f8a5 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Mon, 20 Jul 2020 17:36:42 +0700 Subject: [PATCH 063/350] add epilepsy warning field --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 ++ osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3860f12baa..da4c4ca36b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -91,6 +91,8 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } + public bool EpilepsyWarning { get; set; } + // Editor // This bookmarks stuff is necessary because DB doesn't know how to store int[] [JsonIgnore] diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b30ec0ca2c..fd17e38a4f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -175,6 +175,9 @@ namespace osu.Game.Beatmaps.Formats case @"WidescreenStoryboard": beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; + case @"EpilepsyWarning": + beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; + break; } } From 055e31ddd54b7867589261ae632bc14c5103bf08 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Mon, 20 Jul 2020 18:37:02 +0700 Subject: [PATCH 064/350] update minor --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index da4c4ca36b..b7946d53ca 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -91,6 +91,7 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } + [NotMapped] public bool EpilepsyWarning { get; set; } // Editor diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 069ac69622..f672a4db6c 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -51,8 +51,13 @@ namespace osu.Game.Screens.Play private class EpilepsyWarning : FillFlowContainer { - public EpilepsyWarning() + public EpilepsyWarning(bool warning) { + if (warning) + this.Show(); + else + this.Hide(); + AutoSizeAxes = Axes.Both; Direction = FillDirection.Vertical; Children = new Drawable[] @@ -62,7 +67,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Icon = FontAwesome.Solid.ExclamationTriangle, - Size = new Vector2(40) + Size = new Vector2(40), }, new OsuSpriteText { @@ -194,7 +199,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 }, Current = mods }, - new EpilepsyWarning + new EpilepsyWarning(beatmap.BeatmapInfo.EpilepsyWarning) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From d9fedb293a1e712fa4a4ac5902c9edf1592ba84b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Tue, 21 Jul 2020 15:48:11 +0700 Subject: [PATCH 065/350] add initial test --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 4c73065087..341924ae6d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -35,6 +35,8 @@ namespace osu.Game.Tests.Visual.Gameplay private TestPlayerLoaderContainer container; private TestPlayer player; + private bool EpilepsyWarning = false; + [Resolved] private AudioManager audioManager { get; set; } @@ -55,6 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + Beatmap.Value.BeatmapInfo.EpilepsyWarning = EpilepsyWarning; foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); @@ -240,6 +243,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for player load", () => player.IsLoaded); } + [TestCase(true)] + [TestCase(false)] + public void TestEpilepsyWarning(bool warning) + { + AddStep("change epilepsy warning", () => EpilepsyWarning = warning); + AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + } + private class TestPlayerLoaderContainer : Container { [Cached] From 95f52573f7ca180bfb366aaf5dc9f7fdddddc419 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Date: Tue, 21 Jul 2020 15:58:25 +0700 Subject: [PATCH 066/350] change font size --- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index f672a4db6c..bab141a75e 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -73,13 +73,15 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = "This beatmap contains scenes with rapidly flashing colours." + Text = "This beatmap contains scenes with rapidly flashing colours.", + Font = OsuFont.GetFont(size: 20), }, new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = "Please take caution if you are affected by epilepsy." + Text = "Please take caution if you are affected by epilepsy.", + Font = OsuFont.GetFont(size: 20), } }; } From fea6389f693947dd22d8f4bda9ecc30c33278cdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 12:41:06 +0900 Subject: [PATCH 067/350] Hide HUD elements during break time by default --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 8 ++-- osu.Game/Configuration/HUDVisibilityMode.cs | 17 ++++++++ osu.Game/Configuration/OsuConfigManager.cs | 4 +- .../Sections/Gameplay/GeneralSettings.cs | 6 +-- osu.Game/Screens/Play/HUDOverlay.cs | 42 +++++++++++++++---- osu.Game/Screens/Play/Player.cs | 1 + 6 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Configuration/HUDVisibilityMode.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index c192a7b0e0..e84e3cc930 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -65,17 +65,17 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExternalHideDoesntAffectConfig() { - bool originalConfigValue = false; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.DuringGameplay; createNew(); - AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.ShowInterface)); + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); - AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface)); + AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.HUDVisibilityMode)); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); - AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface)); + AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.HUDVisibilityMode)); } [Test] diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs new file mode 100644 index 0000000000..2b133b1bcf --- /dev/null +++ b/osu.Game/Configuration/HUDVisibilityMode.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum HUDVisibilityMode + { + Never, + + [Description("Hide during breaks")] + DuringGameplay, + + Always + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 268328272c..3ce71e8549 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -85,7 +85,7 @@ namespace osu.Game.Configuration Set(OsuSetting.HitLighting, true); - Set(OsuSetting.ShowInterface, true); + Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.DuringGameplay); Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.FadePlayfieldWhenHealthLow, true); @@ -184,7 +184,7 @@ namespace osu.Game.Configuration AlwaysPlayFirstComboBreak, ScoreMeter, FloatingComments, - ShowInterface, + HUDVisibilityMode, ShowProgressGraph, ShowHealthDisplayWhenCantFail, FadePlayfieldWhenHealthLow, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 93a02ea0e4..af71c4f4e8 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -36,10 +36,10 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Lighten playfield during breaks", Bindable = config.GetBindable(OsuSetting.LightenDuringBreaks) }, - new SettingsCheckbox + new SettingsEnumDropdown { - LabelText = "Show score overlay", - Bindable = config.GetBindable(OsuSetting.ShowInterface) + LabelText = "Score overlay (HUD) mode", + Bindable = config.GetBindable(OsuSetting.HUDVisibilityMode) }, new SettingsCheckbox { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f09745cf71..ef1f80e0d5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play /// public Bindable ShowHud { get; } = new BindableBool(); - private Bindable configShowHud; + private Bindable configVisibilityMode; private readonly Container visibilityContainer; @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Play private readonly Container topScoreContainer; + internal readonly IBindable IsBreakTime = new Bindable(); + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -139,9 +141,9 @@ namespace osu.Game.Screens.Play ModDisplay.Current.Value = mods; - configShowHud = config.GetBindable(OsuSetting.ShowInterface); + configVisibilityMode = config.GetBindable(OsuSetting.HUDVisibilityMode); - if (!configShowHud.Value && !hasShownNotificationOnce) + if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce) { hasShownNotificationOnce = true; @@ -177,15 +179,33 @@ namespace osu.Game.Screens.Play } }, true); - configShowHud.BindValueChanged(visible => - { - if (!ShowHud.Disabled) - ShowHud.Value = visible.NewValue; - }, true); + IsBreakTime.BindValueChanged(_ => updateVisibility()); + configVisibilityMode.BindValueChanged(_ => updateVisibility(), true); replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } + private void updateVisibility() + { + if (ShowHud.Disabled) + return; + + switch (configVisibilityMode.Value) + { + case HUDVisibilityMode.Never: + ShowHud.Value = false; + break; + + case HUDVisibilityMode.DuringGameplay: + ShowHud.Value = replayLoaded.Value || !IsBreakTime.Value; + break; + + case HUDVisibilityMode.Always: + ShowHud.Value = true; + break; + } + } + private void replayLoadedValueChanged(ValueChangedEvent e) { PlayerSettingsOverlay.ReplayLoaded = e.NewValue; @@ -202,6 +222,8 @@ namespace osu.Game.Screens.Play ModDisplay.Delay(2000).FadeOut(200); KeyCounter.Margin = new MarginPadding(10); } + + updateVisibility(); } protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) @@ -222,7 +244,9 @@ namespace osu.Game.Screens.Play switch (e.Key) { case Key.Tab: - configShowHud.Value = !configShowHud.Value; + configVisibilityMode.Value = configVisibilityMode.Value != HUDVisibilityMode.Never + ? HUDVisibilityMode.Never + : HUDVisibilityMode.DuringGameplay; return true; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..50b2d5a021 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -612,6 +612,7 @@ namespace osu.Game.Screens.Play // bind component bindables. Background.IsBreakTime.BindTo(breakTracker.IsBreakTime); + HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime); DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); From 3a97ee4712557862fba5cecb1d625ae684d2fbee Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 9 Aug 2020 16:16:01 -0700 Subject: [PATCH 068/350] Context menu for duplicating multi rooms --- osu.Game/Online/Multiplayer/Room.cs | 50 +++++++++++-------- .../Multi/Lounge/Components/DrawableRoom.cs | 11 +++- .../Multi/Lounge/Components/RoomsContainer.cs | 15 ++++-- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 15 +++++- osu.Game/Screens/Multi/Multiplayer.cs | 4 +- 5 files changed, 67 insertions(+), 28 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 34cf158442..01d9446bf6 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -103,38 +103,48 @@ namespace osu.Game.Online.Multiplayer [JsonIgnore] public readonly Bindable Position = new Bindable(-1); - public void CopyFrom(Room other) + /// + /// Copies the properties from another to this room. + /// + /// The room to copy + /// Whether the copy should exclude information unique to a specific room (i.e. when duplicating a room) + public void CopyFrom(Room other, bool duplicate = false) { - RoomID.Value = other.RoomID.Value; + if (!duplicate) + { + RoomID.Value = other.RoomID.Value; + + if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) + Host.Value = other.Host.Value; + + ChannelId.Value = other.ChannelId.Value; + Status.Value = other.Status.Value; + ParticipantCount.Value = other.ParticipantCount.Value; + EndDate.Value = other.EndDate.Value; + + if (DateTimeOffset.Now >= EndDate.Value) + Status.Value = new RoomStatusEnded(); + + if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) + { + RecentParticipants.Clear(); + RecentParticipants.AddRange(other.RecentParticipants); + } + + Position.Value = other.Position.Value; + } + Name.Value = other.Name.Value; - if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) - Host.Value = other.Host.Value; - - ChannelId.Value = other.ChannelId.Value; - Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; - ParticipantCount.Value = other.ParticipantCount.Value; - EndDate.Value = other.EndDate.Value; - - if (DateTimeOffset.Now >= EndDate.Value) - Status.Value = new RoomStatusEnded(); if (!Playlist.SequenceEqual(other.Playlist)) { Playlist.Clear(); Playlist.AddRange(other.Playlist); } - - if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) - { - RecentParticipants.Clear(); - RecentParticipants.AddRange(other.RecentParticipants); - } - - Position.Value = other.Position.Value; } public bool ShouldSerializeRoomID() => false; diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index 8dd1b239e8..64fbae2503 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -21,10 +21,12 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; using osuTK; using osuTK.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Screens.Multi.Lounge.Components { - public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable + public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable, IHasContextMenu { public const float SELECTION_BORDER_WIDTH = 4; private const float corner_radius = 5; @@ -36,6 +38,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components public event Action StateChanged; + public Action DuplicateRoom; + private readonly Box selectionBox; private CachedModelDependencyContainer dependencies; @@ -232,5 +236,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components Current = name; } } + + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem("Duplicate", MenuItemType.Standard, () => DuplicateRoom?.Invoke(Room)) + }; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 447c99039a..f112dd80ee 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.Multiplayer; using osuTK; +using osu.Game.Graphics.Cursor; namespace osu.Game.Screens.Multi.Lounge.Components { @@ -37,17 +38,24 @@ namespace osu.Game.Screens.Multi.Lounge.Components [Resolved] private IRoomManager roomManager { get; set; } + public Action DuplicateRoom; + public RoomsContainer() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = roomFlow = new FillFlowContainer + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2), + Child = roomFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + } }; } @@ -88,6 +96,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { roomFlow.Add(new DrawableRoom(room) { + DuplicateRoom = DuplicateRoom, Action = () => { if (room == selectedRoom.Value) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index ff7d56a95b..5d68386398 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -62,7 +62,18 @@ namespace osu.Game.Screens.Multi.Lounge RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, Padding = new MarginPadding(10), - Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested } + Child = roomsContainer = new RoomsContainer + { + JoinRequested = joinRequested, + DuplicateRoom = room => + { + Room newRoom = new Room(); + newRoom.CopyFrom(room, true); + newRoom.Name.Value = $"Copy of {room.Name.Value}"; + + Open(newRoom); + } + } }, loadingLayer = new LoadingLayer(roomsContainer), } @@ -126,7 +137,7 @@ namespace osu.Game.Screens.Multi.Lounge if (selectedRoom.Value?.RoomID.Value == null) selectedRoom.Value = new Room(); - music.EnsurePlayingSomething(); + music?.EnsurePlayingSomething(); onReturning(); } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 4912df17b1..cdaeebefb7 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Multi [Cached] private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); - [Resolved] + [Resolved(CanBeNull = true)] private MusicController music { get; set; } [Cached(Type = typeof(IRoomManager))] @@ -350,7 +350,7 @@ namespace osu.Game.Screens.Multi track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; track.Looping = true; - music.EnsurePlayingSomething(); + music?.EnsurePlayingSomething(); } } else From 9e4b9188e1266f1b719e6ba1212aa00ccf1ebbd8 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 15 Aug 2020 13:06:16 -0700 Subject: [PATCH 069/350] Cache LoungeSubScreen, separate method, rename option --- osu.Game/Online/Multiplayer/Room.cs | 56 +++++++++++-------- .../Multi/Lounge/Components/DrawableRoom.cs | 4 +- .../Multi/Lounge/Components/RoomsContainer.cs | 11 +++- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 14 +---- 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 01d9446bf6..5feebe8da1 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -104,47 +104,55 @@ namespace osu.Game.Online.Multiplayer public readonly Bindable Position = new Bindable(-1); /// - /// Copies the properties from another to this room. + /// Create a copy of this room, without information specific to it, such as Room ID or host /// - /// The room to copy - /// Whether the copy should exclude information unique to a specific room (i.e. when duplicating a room) - public void CopyFrom(Room other, bool duplicate = false) + public Room CreateCopy() { - if (!duplicate) + Room newRoom = new Room { - RoomID.Value = other.RoomID.Value; + Name = { Value = Name.Value }, + Availability = { Value = Availability.Value }, + Type = { Value = Type.Value }, + MaxParticipants = { Value = MaxParticipants.Value } + }; - if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) - Host.Value = other.Host.Value; + newRoom.Playlist.AddRange(Playlist); - ChannelId.Value = other.ChannelId.Value; - Status.Value = other.Status.Value; - ParticipantCount.Value = other.ParticipantCount.Value; - EndDate.Value = other.EndDate.Value; - - if (DateTimeOffset.Now >= EndDate.Value) - Status.Value = new RoomStatusEnded(); - - if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) - { - RecentParticipants.Clear(); - RecentParticipants.AddRange(other.RecentParticipants); - } - - Position.Value = other.Position.Value; - } + return newRoom; + } + public void CopyFrom(Room other) + { + RoomID.Value = other.RoomID.Value; Name.Value = other.Name.Value; + if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) + Host.Value = other.Host.Value; + + ChannelId.Value = other.ChannelId.Value; + Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; + ParticipantCount.Value = other.ParticipantCount.Value; + EndDate.Value = other.EndDate.Value; + + if (DateTimeOffset.Now >= EndDate.Value) + Status.Value = new RoomStatusEnded(); if (!Playlist.SequenceEqual(other.Playlist)) { Playlist.Clear(); Playlist.AddRange(other.Playlist); } + + if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) + { + RecentParticipants.Clear(); + RecentParticipants.AddRange(other.RecentParticipants); + } + + Position.Value = other.Position.Value; } public bool ShouldSerializeRoomID() => false; diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index 64fbae2503..db75df6054 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components public event Action StateChanged; - public Action DuplicateRoom; + public Action DuplicateRoom; private readonly Box selectionBox; private CachedModelDependencyContainer dependencies; @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Duplicate", MenuItemType.Standard, () => DuplicateRoom?.Invoke(Room)) + new OsuMenuItem("Create copy", MenuItemType.Standard, DuplicateRoom) }; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index f112dd80ee..206ce8da0f 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -38,7 +38,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components [Resolved] private IRoomManager roomManager { get; set; } - public Action DuplicateRoom; + [Resolved] + private LoungeSubScreen loungeSubScreen { get; set; } public RoomsContainer() { @@ -96,7 +97,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components { roomFlow.Add(new DrawableRoom(room) { - DuplicateRoom = DuplicateRoom, + DuplicateRoom = () => + { + Room newRoom = room.CreateCopy(); + newRoom.Name.Value = $"Copy of {room.Name.Value}"; + + loungeSubScreen.Open(newRoom); + }, Action = () => { if (room == selectedRoom.Value) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 5d68386398..a5b2499c76 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -18,6 +18,7 @@ using osu.Game.Screens.Multi.Match; namespace osu.Game.Screens.Multi.Lounge { + [Cached] public class LoungeSubScreen : MultiplayerSubScreen { public override string Title => "Lounge"; @@ -62,18 +63,7 @@ namespace osu.Game.Screens.Multi.Lounge RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, Padding = new MarginPadding(10), - Child = roomsContainer = new RoomsContainer - { - JoinRequested = joinRequested, - DuplicateRoom = room => - { - Room newRoom = new Room(); - newRoom.CopyFrom(room, true); - newRoom.Name.Value = $"Copy of {room.Name.Value}"; - - Open(newRoom); - } - } + Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested } }, loadingLayer = new LoadingLayer(roomsContainer), } From f5877810588dd76cd30b2ccde309e8c25cccccb7 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 15 Aug 2020 14:27:49 -0700 Subject: [PATCH 070/350] Allow LoungeSubScreen to be null (fix test) --- osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 206ce8da0f..1954d97a8f 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components [Resolved] private IRoomManager roomManager { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private LoungeSubScreen loungeSubScreen { get; set; } public RoomsContainer() @@ -100,9 +100,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components DuplicateRoom = () => { Room newRoom = room.CreateCopy(); - newRoom.Name.Value = $"Copy of {room.Name.Value}"; + if (!newRoom.Name.Value.StartsWith("Copy of ")) + newRoom.Name.Value = $"Copy of {room.Name.Value}"; - loungeSubScreen.Open(newRoom); + loungeSubScreen?.Open(newRoom); }, Action = () => { From 6ff26f6b8c943e1178a5e4ebf78c086501cc75cb Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 24 Sep 2020 12:52:42 -0700 Subject: [PATCH 071/350] Fix anchor of tournament ruleset selector dropdown --- osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index 0e995ca73d..af0043436a 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -73,8 +73,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, new Container { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = Component = CreateComponent().With(d => From 42f666cd24456ac823736fb2a6f5a876c092e735 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 29 Sep 2020 23:04:03 +0930 Subject: [PATCH 072/350] Set icon for SDL desktop window --- osu.Desktop/OsuGameDesktop.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 2079f136d2..659730630a 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -138,6 +138,7 @@ namespace osu.Desktop // SDL2 DesktopWindow case DesktopWindow desktopWindow: desktopWindow.CursorState.Value |= CursorState.Hidden; + desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); desktopWindow.Title = Name; desktopWindow.DragDrop += f => fileDrop(new[] { f }); break; From 7359c422dd377f7b589bbb12ce8662c41060124d Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sat, 3 Oct 2020 12:58:43 +0930 Subject: [PATCH 073/350] Hoist icon stream --- osu.Desktop/OsuGameDesktop.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 659730630a..836b968a67 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -125,12 +125,14 @@ namespace osu.Desktop { base.SetHost(host); + var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"); + switch (host.Window) { // Legacy osuTK DesktopGameWindow case DesktopGameWindow desktopGameWindow: desktopGameWindow.CursorState |= CursorState.Hidden; - desktopGameWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); + desktopGameWindow.SetIconFromStream(iconStream); desktopGameWindow.Title = Name; desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames); break; @@ -138,7 +140,7 @@ namespace osu.Desktop // SDL2 DesktopWindow case DesktopWindow desktopWindow: desktopWindow.CursorState.Value |= CursorState.Hidden; - desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); + desktopWindow.SetIconFromStream(iconStream); desktopWindow.Title = Name; desktopWindow.DragDrop += f => fileDrop(new[] { f }); break; From d87e4c524c1c03539881df35fa97ccf800751aae Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 3 Oct 2020 14:21:40 +0300 Subject: [PATCH 074/350] Test HitResultExtensions methods --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index ace57aad1d..38d2b4a47f 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -53,5 +53,105 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, false)] + [TestCase(HitResult.Miss, true)] + [TestCase(HitResult.Meh, true)] + [TestCase(HitResult.Ok, true)] + [TestCase(HitResult.Good, true)] + [TestCase(HitResult.Great, true)] + [TestCase(HitResult.Perfect, true)] + [TestCase(HitResult.SmallTickMiss, false)] + [TestCase(HitResult.SmallTickHit, false)] + [TestCase(HitResult.LargeTickMiss, true)] + [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SmallBonus, false)] + [TestCase(HitResult.LargeBonus, false)] + public void TestAffectsCombo(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.AffectsCombo() == expectedReturnValue); + } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, false)] + [TestCase(HitResult.Miss, true)] + [TestCase(HitResult.Meh, true)] + [TestCase(HitResult.Ok, true)] + [TestCase(HitResult.Good, true)] + [TestCase(HitResult.Great, true)] + [TestCase(HitResult.Perfect, true)] + [TestCase(HitResult.SmallTickMiss, true)] + [TestCase(HitResult.SmallTickHit, true)] + [TestCase(HitResult.LargeTickMiss, true)] + [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SmallBonus, false)] + [TestCase(HitResult.LargeBonus, false)] + public void TestAffectsAccuracy(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.AffectsAccuracy() == expectedReturnValue); + } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, false)] + [TestCase(HitResult.Miss, false)] + [TestCase(HitResult.Meh, false)] + [TestCase(HitResult.Ok, false)] + [TestCase(HitResult.Good, false)] + [TestCase(HitResult.Great, false)] + [TestCase(HitResult.Perfect, false)] + [TestCase(HitResult.SmallTickMiss, false)] + [TestCase(HitResult.SmallTickHit, false)] + [TestCase(HitResult.LargeTickMiss, false)] + [TestCase(HitResult.LargeTickHit, false)] + [TestCase(HitResult.SmallBonus, true)] + [TestCase(HitResult.LargeBonus, true)] + public void TestIsBonus(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.IsBonus() == expectedReturnValue); + } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, true)] + [TestCase(HitResult.Miss, false)] + [TestCase(HitResult.Meh, true)] + [TestCase(HitResult.Ok, true)] + [TestCase(HitResult.Good, true)] + [TestCase(HitResult.Great, true)] + [TestCase(HitResult.Perfect, true)] + [TestCase(HitResult.SmallTickMiss, false)] + [TestCase(HitResult.SmallTickHit, true)] + [TestCase(HitResult.LargeTickMiss, false)] + [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SmallBonus, true)] + [TestCase(HitResult.LargeBonus, true)] + public void TestIsHit(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.IsHit() == expectedReturnValue); + } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, false)] + [TestCase(HitResult.Miss, true)] + [TestCase(HitResult.Meh, true)] + [TestCase(HitResult.Ok, true)] + [TestCase(HitResult.Good, true)] + [TestCase(HitResult.Great, true)] + [TestCase(HitResult.Perfect, true)] + [TestCase(HitResult.SmallTickMiss, true)] + [TestCase(HitResult.SmallTickHit, true)] + [TestCase(HitResult.LargeTickMiss, true)] + [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SmallBonus, true)] + [TestCase(HitResult.LargeBonus, true)] + public void TestIsScorable(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.IsScorable() == expectedReturnValue); + } } } From 01636d501a0fc5dbf4a1959c23e9d3eaf577817f Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 18:36:15 +0300 Subject: [PATCH 075/350] Add MinResults test and starts of score portion tests --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 38d2b4a47f..52848cb716 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -9,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -54,6 +56,44 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + [TestCase(ScoringMode.Standardised, "osu", typeof(HitCircle), HitResult.Great, 575_000)] + public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, string rulesetName, Type hitObjectType, HitResult hitResult, int expectedScore) + { + IBeatmap fourObjectBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) + { + HitObjects = new List(Enumerable.Repeat((HitObject)Activator.CreateInstance(hitObjectType), 4)) + }; + scoreProcessor.Mode.Value = scoringMode; + scoreProcessor.ApplyBeatmap(fourObjectBeatmap); + + for (int i = 0; i < 4; i++) + { + var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], new Judgement()) + { + Type = i == 2 ? HitResult.Miss : hitResult + }; + scoreProcessor.ApplyResult(judgementResult); + } + + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + } + + [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] + [TestCase(HitResult.Meh, HitResult.Miss)] + [TestCase(HitResult.Ok, HitResult.Miss)] + [TestCase(HitResult.Good, HitResult.Miss)] + [TestCase(HitResult.Great, HitResult.Miss)] + [TestCase(HitResult.Perfect, HitResult.Miss)] + [TestCase(HitResult.SmallTickHit, HitResult.SmallTickMiss)] + [TestCase(HitResult.LargeTickHit, HitResult.LargeTickMiss)] + [TestCase(HitResult.SmallBonus, HitResult.IgnoreMiss)] + [TestCase(HitResult.LargeBonus, HitResult.IgnoreMiss)] + public void TestMinResults(HitResult hitResult, HitResult expectedMinResult) + { + var result = new JudgementResult(new HitObject(), new TestJudgement(hitResult)); + Assert.IsTrue(result.Judgement.MinResult == expectedMinResult); + } + [TestCase(HitResult.None, false)] [TestCase(HitResult.IgnoreMiss, false)] [TestCase(HitResult.IgnoreHit, false)] @@ -153,5 +193,15 @@ namespace osu.Game.Tests.Rulesets.Scoring { Assert.IsTrue(hitResult.IsScorable() == expectedReturnValue); } + + private class TestJudgement : Judgement + { + public override HitResult MaxResult { get; } + + public TestJudgement(HitResult maxResult) + { + MaxResult = maxResult; + } + } } } From bdc84c529114b14957aeb9959f2fddca675956f2 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 19:53:24 +0300 Subject: [PATCH 076/350] Finish score portion tests for standardised scoring mode --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 52848cb716..d38a2a89cc 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.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; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -10,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -56,12 +54,23 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } - [TestCase(ScoringMode.Standardised, "osu", typeof(HitCircle), HitResult.Great, 575_000)] - public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, string rulesetName, Type hitObjectType, HitResult hitResult, int expectedScore) + [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] + [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] + [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] + [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: idk, this should be 225_000 from accuracy portion + [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] + [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 150)] + public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { - IBeatmap fourObjectBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) + var minResult = new JudgementResult(new HitObject(), new TestJudgement(hitResult)).Judgement.MinResult; + + IBeatmap fourObjectBeatmap = new TestBeatmap(new RulesetInfo()) { - HitObjects = new List(Enumerable.Repeat((HitObject)Activator.CreateInstance(hitObjectType), 4)) + HitObjects = new List(Enumerable.Repeat(new TestHitObject(maxResult), 4)) }; scoreProcessor.Mode.Value = scoringMode; scoreProcessor.ApplyBeatmap(fourObjectBeatmap); @@ -70,7 +79,7 @@ namespace osu.Game.Tests.Rulesets.Scoring { var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], new Judgement()) { - Type = i == 2 ? HitResult.Miss : hitResult + Type = i == 2 ? minResult : hitResult }; scoreProcessor.ApplyResult(judgementResult); } @@ -203,5 +212,20 @@ namespace osu.Game.Tests.Rulesets.Scoring MaxResult = maxResult; } } + + private class TestHitObject : HitObject + { + private readonly HitResult maxResult; + + public override Judgement CreateJudgement() + { + return new TestJudgement(maxResult); + } + + public TestHitObject(HitResult maxResult) + { + this.maxResult = maxResult; + } + } } } From 879131c6752fcad96f42fe4bb17de0dc75b4095a Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 20:02:33 +0300 Subject: [PATCH 077/350] Also test Goods and Perfects --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index d38a2a89cc..e1afb82e81 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -57,14 +57,16 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] + [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 3_350_000 / 7.0)] [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] + [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: idk, this should be 225_000 from accuracy portion [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 150)] - public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) + public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, double expectedScore) { var minResult = new JudgementResult(new HitObject(), new TestJudgement(hitResult)).Judgement.MinResult; From 6684a98a321ff086811225f44b1d3c13b8039ec2 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 20:24:42 +0300 Subject: [PATCH 078/350] Also test Classic scoring --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index e1afb82e81..dd364a645c 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -61,11 +61,23 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: idk, this should be 225_000 from accuracy portion + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: this should probably be 225_000 from accuracy portion [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 150)] + [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 3744 / 7.0)] + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, double expectedScore) { var minResult = new JudgementResult(new HitObject(), new TestJudgement(hitResult)).Judgement.MinResult; From a31fe5f5ff9d23532c2b3c681a335236e51a72a7 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 22:26:18 +0300 Subject: [PATCH 079/350] Temporarily remove SmallTickHit tests --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index dd364a645c..2ad9837654 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -60,8 +60,6 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 3_350_000 / 7.0)] [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: this should probably be 225_000 from accuracy portion [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] @@ -72,8 +70,6 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 3744 / 7.0)] [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] From 4c9840ccf1f8bf91801f099b1c3d8ac43f9cde2c Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 22:57:55 +0300 Subject: [PATCH 080/350] Apply review suggestions --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 2ad9837654..1181d82d09 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, double expectedScore) { - var minResult = new JudgementResult(new HitObject(), new TestJudgement(hitResult)).Judgement.MinResult; + var minResult = new TestJudgement(hitResult).MinResult; IBeatmap fourObjectBeatmap = new TestBeatmap(new RulesetInfo()) { @@ -109,8 +109,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, HitResult.IgnoreMiss)] public void TestMinResults(HitResult hitResult, HitResult expectedMinResult) { - var result = new JudgementResult(new HitObject(), new TestJudgement(hitResult)); - Assert.IsTrue(result.Judgement.MinResult == expectedMinResult); + Assert.AreEqual(expectedMinResult, new TestJudgement(hitResult).MinResult); } [TestCase(HitResult.None, false)] @@ -130,7 +129,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, false)] public void TestAffectsCombo(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.AffectsCombo() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.AffectsCombo()); } [TestCase(HitResult.None, false)] @@ -150,7 +149,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, false)] public void TestAffectsAccuracy(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.AffectsAccuracy() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.AffectsAccuracy()); } [TestCase(HitResult.None, false)] @@ -170,7 +169,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, true)] public void TestIsBonus(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.IsBonus() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.IsBonus()); } [TestCase(HitResult.None, false)] @@ -190,7 +189,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, true)] public void TestIsHit(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.IsHit() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.IsHit()); } [TestCase(HitResult.None, false)] @@ -210,7 +209,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, true)] public void TestIsScorable(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.IsScorable() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.IsScorable()); } private class TestJudgement : Judgement From 5e314c0662919757871761414682e3389886bfa3 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 22:58:09 +0300 Subject: [PATCH 081/350] Write new test for small ticks --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 1181d82d09..f81ab6c866 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -97,6 +97,40 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 6_850_000 / 7.0)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 6_400_000 / 7.0)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 1950 / 7.0)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 1500 / 7.0)] + public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, double expectedScore) + { + IEnumerable hitObjects = Enumerable + .Repeat(new TestHitObject(HitResult.SmallTickHit), 4) + .Append(new TestHitObject(HitResult.Ok)); + IBeatmap fiveObjectBeatmap = new TestBeatmap(new RulesetInfo()) + { + HitObjects = hitObjects.ToList() + }; + scoreProcessor.Mode.Value = scoringMode; + scoreProcessor.ApplyBeatmap(fiveObjectBeatmap); + + for (int i = 0; i < 4; i++) + { + var judgementResult = new JudgementResult(fiveObjectBeatmap.HitObjects[i], new Judgement()) + { + Type = i == 2 ? HitResult.SmallTickMiss : hitResult + }; + scoreProcessor.ApplyResult(judgementResult); + } + + var lastJudgementResult = new JudgementResult(fiveObjectBeatmap.HitObjects.Last(), new Judgement()) + { + Type = HitResult.Ok + }; + scoreProcessor.ApplyResult(lastJudgementResult); + + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + } + [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] [TestCase(HitResult.Meh, HitResult.Miss)] [TestCase(HitResult.Ok, HitResult.Miss)] From 74af7cc5036911b47238d9cde41691c0bcf5cf83 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 7 Oct 2020 17:00:00 +0300 Subject: [PATCH 082/350] Rework ScoreProcessor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7a5b707357..ca6a8622f7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return getBonusScore(statistics) + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); + return getBonusScore(statistics) + (accuracyRatio * Math.Max(1, maxCombo) * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); } } @@ -267,12 +267,6 @@ namespace osu.Game.Rulesets.Scoring { maxHighestCombo = HighestCombo.Value; maxBaseScore = baseScore; - - if (maxBaseScore == 0 || maxHighestCombo == 0) - { - Mode.Value = ScoringMode.Classic; - Mode.Disabled = true; - } } baseScore = 0; From 2b6e4e575e2668e102f491a66ff070cac66c5fdd Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 7 Oct 2020 17:04:55 +0300 Subject: [PATCH 083/350] Award max combo portion score if max achievable is 0 --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ca6a8622f7..9bfd737f7e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Scoring { return GetScore(mode, maxHighestCombo, maxBaseScore > 0 ? baseScore / maxBaseScore : 0, - maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 0, + maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 1, scoreResultCounts); } From 6113557acc346a572d710229553b133a9ebfdd91 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 7 Oct 2020 17:11:48 +0300 Subject: [PATCH 084/350] Add back small tick tests --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index f81ab6c866..dd191b03c2 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -60,16 +60,20 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 3_350_000 / 7.0)] [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 150)] + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 3744 / 7.0)] [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] From 7109c3b6cd1777d5aa1b3eafceb547ff6de7742f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Oct 2020 21:06:24 +0200 Subject: [PATCH 085/350] Rename variable as suggested --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 9bfd737f7e..33271d9689 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Scoring private readonly double accuracyPortion; private readonly double comboPortion; - private int maxHighestCombo; + private int maxAchievableCombo; private double maxBaseScore; private double rollingMaxBaseScore; private double baseScore; @@ -195,9 +195,9 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(mode, maxHighestCombo, + return GetScore(mode, maxAchievableCombo, maxBaseScore > 0 ? baseScore / maxBaseScore : 0, - maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 1, + maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1, scoreResultCounts); } @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Scoring if (storeResults) { - maxHighestCombo = HighestCombo.Value; + maxAchievableCombo = HighestCombo.Value; maxBaseScore = baseScore; } From 2d070934d912b9b05d0d587a30e80dee5ff0d838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Oct 2020 21:12:48 +0200 Subject: [PATCH 086/350] Add test coverage for empty beatmaps --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index dd191b03c2..e89562f893 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -135,6 +135,17 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + [Test] + public void TestEmptyBeatmap( + [Values(ScoringMode.Standardised, ScoringMode.Classic)] + ScoringMode scoringMode) + { + scoreProcessor.Mode.Value = scoringMode; + scoreProcessor.ApplyBeatmap(new TestBeatmap(new RulesetInfo())); + + Assert.IsTrue(Precision.AlmostEquals(0, scoreProcessor.TotalScore.Value)); + } + [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] [TestCase(HitResult.Meh, HitResult.Miss)] [TestCase(HitResult.Ok, HitResult.Miss)] From 696e3d53afdc3754c7d8a2565d022520664d1c4c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 20:50:09 +0900 Subject: [PATCH 087/350] Fix slider samples being overwritten by the last node --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 6 ++++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 11 ++++++----- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 3 --- osu.Game/Rulesets/Objects/Types/IHasRepeats.cs | 10 ++++++++++ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 6b8b70ed54..e209d012fa 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Catch.Objects Volume = s.Volume }).ToList(); + int nodeIndex = 0; SliderEventDescriptor? lastEvent = null; foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) @@ -105,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.Objects case SliderEventType.Repeat: AddNested(new Fruit { - Samples = Samples, + Samples = this.GetNodeSamples(nodeIndex++), StartTime = e.Time, X = X + Path.PositionAt(e.PathProgress).X, }); @@ -119,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Duration { get => this.SpanCount() * Path.Distance / Velocity; - set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. + set => throw new NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } public double EndTime => StartTime + Duration; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 917382eccf..755ce0866a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -137,6 +137,10 @@ namespace osu.Game.Rulesets.Osu.Objects Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; + + // The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to. + // For now, the samples are attached to and played by the slider itself at the correct end time. + Samples = this.GetNodeSamples(repeatCount + 1); } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) @@ -230,15 +234,12 @@ namespace osu.Game.Rulesets.Osu.Objects tick.Samples = sampleList; foreach (var repeat in NestedHitObjects.OfType()) - repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1); + repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1); if (HeadCircle != null) - HeadCircle.Samples = getNodeSamples(0); + HeadCircle.Samples = this.GetNodeSamples(0); } - private IList getNodeSamples(int nodeIndex) => - nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; - public override Judgement CreateJudgement() => new OsuIgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9afc0ecaf4..f6adeced96 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -184,9 +184,6 @@ namespace osu.Game.Rulesets.Objects.Legacy nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples); - - // The samples are played when the slider ends, which is the last node - result.Samples = nodeSamples[^1]; } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 7a3fb16196..674e2aee88 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -35,5 +35,15 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The object that has repeats. public static int SpanCount(this IHasRepeats obj) => obj.RepeatCount + 1; + + /// + /// Retrieves the samples at a particular node in a object. + /// + /// The . + /// The node to attempt to retrieve the samples at. + /// The samples at the given node index, or 's default samples if the given node doesn't exist. + public static IList GetNodeSamples(this T obj, int nodeIndex) + where T : HitObject, IHasRepeats + => nodeIndex < obj.NodeSamples.Count ? obj.NodeSamples[nodeIndex] : obj.Samples; } } From d536a1f75e71a8075334b941aff91b9ab6c737c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:04:56 +0900 Subject: [PATCH 088/350] Fix breaks being culled too early --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 +------ osu.Game/Rulesets/Mods/ModFlashlight.cs | 3 +++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b30ec0ca2c..6dadbbd2da 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -307,12 +307,7 @@ namespace osu.Game.Beatmaps.Formats double start = getOffsetTime(Parsing.ParseDouble(split[1])); double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))); - var breakEvent = new BreakPeriod(start, end); - - if (!breakEvent.HasEffect) - return; - - beatmap.Breaks.Add(breakEvent); + beatmap.Breaks.Add(new BreakPeriod(start, end)); break; } } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 6e94a84e7d..08f2ccb75c 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -107,6 +107,9 @@ namespace osu.Game.Rulesets.Mods { foreach (var breakPeriod in Breaks) { + if (!breakPeriod.HasEffect) + continue; + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); From 4d0e4f4adeb0fb1c5c7ac0312b054dd4312d21fb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:11:12 +0900 Subject: [PATCH 089/350] Fix incorrect initial density --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 524ea27efa..c0fbd47899 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -116,7 +116,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps prevNoteTimes.RemoveAt(0); prevNoteTimes.Add(newNoteTime); - density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count; + if (prevNoteTimes.Count >= 2) + density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count; } private double lastTime; From 9d09503ace3762d213bc15a664c5f02c7d9c984c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:12:38 +0900 Subject: [PATCH 090/350] Fix spinner conversion not considering stacking + forced initial column --- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Legacy/EndTimeObjectPatternGenerator.cs | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index c0fbd47899..b17ab3f375 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps case IHasDuration endTimeData: { - conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); + conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); recordNote(endTimeData.EndTime, new Vector2(256, 192)); computeDensity(endTimeData.EndTime); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index d5286a3779..f816a70ab3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -14,12 +14,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { internal class EndTimeObjectPatternGenerator : PatternGenerator { - private readonly double endTime; + private readonly int endTime; + private readonly PatternType convertType; - public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap) - : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) + public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) + : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { - endTime = (HitObject as IHasDuration)?.EndTime ?? 0; + endTime = (int)((HitObject as IHasDuration)?.EndTime ?? 0); + + convertType = PreviousPattern.ColumnWithObjects == TotalColumns + ? PatternType.None + : PatternType.ForceNotStack; } public override IEnumerable Generate() @@ -40,18 +45,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy break; case 8: - addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold); + addToPattern(pattern, getRandomColumn(), generateHold); break; default: - if (TotalColumns > 0) - addToPattern(pattern, GetRandomColumn(), generateHold); + addToPattern(pattern, getRandomColumn(0), generateHold); break; } return pattern; } + private int getRandomColumn(int? lowerBound = null) + { + if ((convertType & PatternType.ForceNotStack) > 0) + return FindAvailableColumn(GetRandomColumn(lowerBound), lowerBound, patterns: PreviousPattern); + + return FindAvailableColumn(GetRandomColumn(lowerBound), lowerBound); + } + /// /// Constructs and adds a note to a pattern. /// From 5f19081db69efe63a0f96a52771d3cefbffb661e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:20:00 +0900 Subject: [PATCH 091/350] Fix incorrect probability calculation for hitobject conversion --- .../Legacy/HitObjectPatternGenerator.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 84f950997d..bc4ab55767 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -397,7 +397,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy case 4: centreProbability = 0; - p2 = Math.Min(p2 * 2, 0.2); + + // Stable requires rngValue > x, which is an inverse-probability. Lazer uses true probability (1 - x). + // But multiplying this value by 2 (stable) is not the same operation as dividing it by 2 (lazer), + // so it needs to be converted to from a probability and then back after the multiplication. + p2 = 1 - Math.Max((1 - p2) * 2, 0.8); p3 = 0; break; @@ -408,11 +412,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy case 6: centreProbability = 0; - p2 = Math.Min(p2 * 2, 0.5); - p3 = Math.Min(p3 * 2, 0.15); + + // Stable requires rngValue > x, which is an inverse-probability. Lazer uses true probability (1 - x). + // But multiplying this value by 2 (stable) is not the same operation as dividing it by 2 (lazer), + // so it needs to be converted to from a probability and then back after the multiplication. + p2 = 1 - Math.Max((1 - p2) * 2, 0.5); + p3 = 1 - Math.Max((1 - p3) * 2, 0.85); break; } + // The stable values were allowed to exceed 1, which indicate <0% probability. + // These values needs to be clamped otherwise GetRandomNoteCount() will throw an exception. + p2 = Math.Clamp(p2, 0, 1); + p3 = Math.Clamp(p3, 0, 1); + double centreVal = Random.NextDouble(); int noteCount = GetRandomNoteCount(p2, p3); From 08f3481b592c74a2151632661759c9e0380a2d65 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:22:13 +0900 Subject: [PATCH 092/350] Use integer calculations to replicate stable's slider conversion --- .../Legacy/DistanceObjectPatternGenerator.cs | 101 ++++++++++-------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index fe146c5324..415201951b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { @@ -25,8 +26,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// private const float osu_base_scoring_distance = 100; - public readonly double EndTime; - public readonly double SegmentDuration; + public readonly int StartTime; + public readonly int EndTime; + public readonly int SegmentDuration; public readonly int SpanCount; private PatternType convertType; @@ -41,20 +43,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var distanceData = hitObject as IHasDistance; var repeatsData = hitObject as IHasRepeats; - SpanCount = repeatsData?.SpanCount() ?? 1; + Debug.Assert(distanceData != null); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); - // The true distance, accounting for any repeats - double distance = (distanceData?.Distance ?? 0) * SpanCount; - // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; - // The duration of the osu! hit object - double osuDuration = distance / osuVelocity; + double beatLength; +#pragma warning disable 618 + if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint) +#pragma warning restore 618 + beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; + else + beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; - EndTime = hitObject.StartTime + osuDuration; - SegmentDuration = (EndTime - HitObject.StartTime) / SpanCount; + SpanCount = repeatsData?.SpanCount() ?? 1; + + StartTime = (int)Math.Round(hitObject.StartTime); + EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier); + + SegmentDuration = (EndTime - StartTime) / SpanCount; } public override IEnumerable Generate() @@ -76,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy foreach (var obj in originalPattern.HitObjects) { - if (!Precision.AlmostEquals(EndTime, obj.GetEndTime())) + if (EndTime != (int)Math.Round(obj.GetEndTime())) intermediatePattern.Add(obj); else endTimePattern.Add(obj); @@ -91,35 +98,35 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (TotalColumns == 1) { var pattern = new Pattern(); - addToPattern(pattern, 0, HitObject.StartTime, EndTime); + addToPattern(pattern, 0, StartTime, EndTime); return pattern; } if (SpanCount > 1) { if (SegmentDuration <= 90) - return generateRandomHoldNotes(HitObject.StartTime, 1); + return generateRandomHoldNotes(StartTime, 1); if (SegmentDuration <= 120) { convertType |= PatternType.ForceNotStack; - return generateRandomNotes(HitObject.StartTime, SpanCount + 1); + return generateRandomNotes(StartTime, SpanCount + 1); } if (SegmentDuration <= 160) - return generateStair(HitObject.StartTime); + return generateStair(StartTime); if (SegmentDuration <= 200 && ConversionDifficulty > 3) - return generateRandomMultipleNotes(HitObject.StartTime); + return generateRandomMultipleNotes(StartTime); - double duration = EndTime - HitObject.StartTime; + double duration = EndTime - StartTime; if (duration >= 4000) - return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); + return generateNRandomNotes(StartTime, 0.23, 0, 0); if (SegmentDuration > 400 && SpanCount < TotalColumns - 1 - RandomStart) - return generateTiledHoldNotes(HitObject.StartTime); + return generateTiledHoldNotes(StartTime); - return generateHoldAndNormalNotes(HitObject.StartTime); + return generateHoldAndNormalNotes(StartTime); } if (SegmentDuration <= 110) @@ -128,37 +135,37 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy convertType |= PatternType.ForceNotStack; else convertType &= ~PatternType.ForceNotStack; - return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2); + return generateRandomNotes(StartTime, SegmentDuration < 80 ? 1 : 2); } if (ConversionDifficulty > 6.5) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); + return generateNRandomNotes(StartTime, 0.78, 0.3, 0); - return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); + return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03); } if (ConversionDifficulty > 4) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); + return generateNRandomNotes(StartTime, 0.43, 0.08, 0); - return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); + return generateNRandomNotes(StartTime, 0.56, 0.18, 0); } if (ConversionDifficulty > 2.5) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); + return generateNRandomNotes(StartTime, 0.3, 0, 0); - return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); + return generateNRandomNotes(StartTime, 0.37, 0.08, 0); } if (convertType.HasFlag(PatternType.LowProbability)) - return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); + return generateNRandomNotes(StartTime, 0.17, 0, 0); - return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); + return generateNRandomNotes(StartTime, 0.27, 0, 0); } /// @@ -167,7 +174,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Start time of each hold note. /// Number of hold notes. /// The containing the hit objects. - private Pattern generateRandomHoldNotes(double startTime, int noteCount) + private Pattern generateRandomHoldNotes(int startTime, int noteCount) { // - - - - // ■ - ■ ■ @@ -202,7 +209,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The start time. /// The number of notes. /// The containing the hit objects. - private Pattern generateRandomNotes(double startTime, int noteCount) + private Pattern generateRandomNotes(int startTime, int noteCount) { // - - - - // x - - - @@ -234,7 +241,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The start time. /// The containing the hit objects. - private Pattern generateStair(double startTime) + private Pattern generateStair(int startTime) { // - - - - // x - - - @@ -286,7 +293,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The start time. /// The containing the hit objects. - private Pattern generateRandomMultipleNotes(double startTime) + private Pattern generateRandomMultipleNotes(int startTime) { // - - - - // x - - - @@ -329,7 +336,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The probability required for 3 hold notes to be generated. /// The probability required for 4 hold notes to be generated. /// The containing the hit objects. - private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4) + private Pattern generateNRandomNotes(int startTime, double p2, double p3, double p4) { // - - - - // ■ - ■ ■ @@ -366,7 +373,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH; bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); - canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); + canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample); if (canGenerateTwoNotes) p2 = 1; @@ -379,7 +386,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The first hold note start time. /// The containing the hit objects. - private Pattern generateTiledHoldNotes(double startTime) + private Pattern generateTiledHoldNotes(int startTime) { // - - - - // ■ ■ ■ ■ @@ -394,6 +401,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int columnRepeat = Math.Min(SpanCount, TotalColumns); + // Due to integer rounding, this is not guaranteed to be the same as EndTime (the class-level variable). + int endTime = startTime + SegmentDuration * SpanCount; + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); @@ -401,7 +411,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy for (int i = 0; i < columnRepeat; i++) { nextColumn = FindAvailableColumn(nextColumn, pattern); - addToPattern(pattern, nextColumn, startTime, EndTime); + addToPattern(pattern, nextColumn, startTime, endTime); startTime += SegmentDuration; } @@ -413,7 +423,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The start time of notes. /// The containing the hit objects. - private Pattern generateHoldAndNormalNotes(double startTime) + private Pattern generateHoldAndNormalNotes(int startTime) { // - - - - // ■ x x - @@ -448,7 +458,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy for (int i = 0; i <= SpanCount; i++) { - if (!(ignoreHead && startTime == HitObject.StartTime)) + if (!(ignoreHead && startTime == StartTime)) { for (int j = 0; j < noteCount; j++) { @@ -471,19 +481,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The time to retrieve the sample info list from. /// - private IList sampleInfoListAt(double time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples; + private IList sampleInfoListAt(int time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples; /// /// Retrieves the list of node samples that occur at time greater than or equal to . /// /// The time to retrieve node samples at. - private List> nodeSamplesAt(double time) + private List> nodeSamplesAt(int time) { if (!(HitObject is IHasPathWithRepeats curveData)) return null; - // mathematically speaking this should be a whole number always, but floating-point arithmetic is not so kind - var index = (int)Math.Round(SegmentDuration == 0 ? 0 : (time - HitObject.StartTime) / SegmentDuration, MidpointRounding.AwayFromZero); + var index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration; // avoid slicing the list & creating copies, if at all possible. return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); @@ -496,7 +505,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The column to add the note to. /// The start time of the note. /// The end time of the note (set to for a non-hold note). - private void addToPattern(Pattern pattern, int column, double startTime, double endTime) + private void addToPattern(Pattern pattern, int column, int startTime, int endTime) { ManiaHitObject newObject; From 485a951281f62bb3ff7afad7b369e4442b61ab24 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:42:43 +0900 Subject: [PATCH 093/350] Expose current strain and retrieval of peak strain --- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 25 ++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 227f2f4018..1063a24b27 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -41,7 +41,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// protected readonly LimitedCapacityStack Previous = new LimitedCapacityStack(2); // Contained objects not used yet - private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. + /// + /// The current strain level. + /// + protected double CurrentStrain { get; private set; } = 1; + private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. private readonly List strainPeaks = new List(); @@ -51,10 +55,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public void Process(DifficultyHitObject current) { - currentStrain *= strainDecay(current.DeltaTime); - currentStrain += StrainValueOf(current) * SkillMultiplier; + CurrentStrain *= strainDecay(current.DeltaTime); + CurrentStrain += StrainValueOf(current) * SkillMultiplier; - currentSectionPeak = Math.Max(currentStrain, currentSectionPeak); + currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak); Previous.Push(current); } @@ -71,15 +75,22 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// Sets the initial strain level for a new section. /// - /// The beginning of the new section in milliseconds. - public void StartNewSectionFrom(double offset) + /// The beginning of the new section in milliseconds. + public void StartNewSectionFrom(double time) { // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. if (Previous.Count > 0) - currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime); + currentSectionPeak = GetPeakStrain(time); } + /// + /// Retrieves the peak strain at a point in time. + /// + /// The time to retrieve the peak strain at. + /// The peak strain. + protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].BaseObject.StartTime); + /// /// Returns the calculated difficulty value representing all processed s. /// From 8f37d2290a4321bc569bc73f30cdbdb7755f1ed7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:43:46 +0900 Subject: [PATCH 094/350] Expose sorting of hitobjects --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 1902de5bda..e80a4e4b1c 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Difficulty if (!beatmap.HitObjects.Any()) return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); - var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, clockRate).OrderBy(h => h.BaseObject.StartTime).ToList(); + var difficultyHitObjects = SortObjects(CreateDifficultyHitObjects(beatmap, clockRate)).ToList(); double sectionLength = SectionLength * clockRate; @@ -100,6 +100,14 @@ namespace osu.Game.Rulesets.Difficulty return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); } + /// + /// Sorts a given set of s. + /// + /// The s to sort. + /// The sorted s. + protected virtual IEnumerable SortObjects(IEnumerable input) + => input.OrderBy(h => h.BaseObject.StartTime); + /// /// Creates all combinations which adjust the difficulty. /// From 5017c92fe84973d58f3fa6c4c90af0b0858c2591 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:47:34 +0900 Subject: [PATCH 095/350] Combine mania skills --- .../Difficulty/ManiaDifficultyCalculator.cs | 47 +---------- .../Difficulty/Skills/Individual.cs | 47 ----------- .../Difficulty/Skills/Overall.cs | 56 ------------- .../Difficulty/Skills/Strain.cs | 80 +++++++++++++++++++ 4 files changed, 84 insertions(+), 146 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs delete mode 100644 osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs create mode 100644 osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index b08c520c54..7dd1755742 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty return new ManiaDifficultyAttributes { - StarRating = difficultyValue(skills) * star_scaling_factor, + StarRating = skills[0].DifficultyValue() * star_scaling_factor, Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate, @@ -49,55 +49,16 @@ namespace osu.Game.Rulesets.Mania.Difficulty }; } - private double difficultyValue(Skill[] skills) - { - // Preprocess the strains to find the maximum overall + individual (aggregate) strain from each section - var overall = skills.OfType().Single(); - var aggregatePeaks = new List(Enumerable.Repeat(0.0, overall.StrainPeaks.Count)); - - foreach (var individual in skills.OfType()) - { - for (int i = 0; i < individual.StrainPeaks.Count; i++) - { - double aggregate = individual.StrainPeaks[i] + overall.StrainPeaks[i]; - - if (aggregate > aggregatePeaks[i]) - aggregatePeaks[i] = aggregate; - } - } - - aggregatePeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - double difficulty = 0; - double weight = 1; - - // Difficulty is the weighted sum of the highest strains from every section. - foreach (double strain in aggregatePeaks) - { - difficulty += strain * weight; - weight *= 0.9; - } - - return difficulty; - } - protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { for (int i = 1; i < beatmap.HitObjects.Count; i++) yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); } - protected override Skill[] CreateSkills(IBeatmap beatmap) + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { - int columnCount = ((ManiaBeatmap)beatmap).TotalColumns; - - var skills = new List { new Overall(columnCount) }; - - for (int i = 0; i < columnCount; i++) - skills.Add(new Individual(i, columnCount)); - - return skills.ToArray(); - } + new Strain(((ManiaBeatmap)beatmap).TotalColumns) + }; protected override Mod[] DifficultyAdjustmentMods { diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs deleted file mode 100644 index 4f7ab87fad..0000000000 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using osu.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Mania.Difficulty.Skills -{ - public class Individual : Skill - { - protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0.125; - - private readonly double[] holdEndTimes; - - private readonly int column; - - public Individual(int column, int columnCount) - { - this.column = column; - - holdEndTimes = new double[columnCount]; - } - - protected override double StrainValueOf(DifficultyHitObject current) - { - var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = maniaCurrent.BaseObject.GetEndTime(); - - try - { - if (maniaCurrent.BaseObject.Column != column) - return 0; - - // We give a slight bonus if something is held meanwhile - return holdEndTimes.Any(t => t > endTime) ? 2.5 : 2; - } - finally - { - holdEndTimes[maniaCurrent.BaseObject.Column] = endTime; - } - } - } -} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs deleted file mode 100644 index bbbb93fd8b..0000000000 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Mania.Difficulty.Skills -{ - public class Overall : Skill - { - protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0.3; - - private readonly double[] holdEndTimes; - - private readonly int columnCount; - - public Overall(int columnCount) - { - this.columnCount = columnCount; - - holdEndTimes = new double[columnCount]; - } - - protected override double StrainValueOf(DifficultyHitObject current) - { - var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = maniaCurrent.BaseObject.GetEndTime(); - - double holdFactor = 1.0; // Factor in case something else is held - double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly - - for (int i = 0; i < columnCount; i++) - { - // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... - if (current.BaseObject.StartTime < holdEndTimes[i] && endTime > holdEndTimes[i]) - holdAddition = 1.0; - - // ... this addition only is valid if there is _no_ other note with the same ending. - // Releasing multiple notes at the same time is just as easy as releasing one - if (endTime == holdEndTimes[i]) - holdAddition = 0; - - // We give a slight bonus if something is held meanwhile - if (holdEndTimes[i] > endTime) - holdFactor = 1.25; - } - - holdEndTimes[maniaCurrent.BaseObject.Column] = endTime; - - return (1 + holdAddition) * holdFactor; - } - } -} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs new file mode 100644 index 0000000000..7ebc1ff752 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Utils; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Difficulty.Skills +{ + public class Strain : Skill + { + private const double individual_decay_base = 0.125; + private const double overall_decay_base = 0.30; + + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 1; + + private readonly double[] holdEndTimes; + private readonly double[] individualStrains; + + private double individualStrain; + private double overallStrain; + + public Strain(int totalColumns) + { + holdEndTimes = new double[totalColumns]; + individualStrains = new double[totalColumns]; + overallStrain = 1; + } + + protected override double StrainValueOf(DifficultyHitObject current) + { + var maniaCurrent = (ManiaDifficultyHitObject)current; + var endTime = maniaCurrent.BaseObject.GetEndTime(); + var column = maniaCurrent.BaseObject.Column; + + double holdFactor = 1.0; // Factor to all additional strains in case something else is held + double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly + + // Fill up the holdEndTimes array + for (int i = 0; i < holdEndTimes.Length; ++i) + { + // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... + if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.BaseObject.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1)) + holdAddition = 1.0; + + // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 + if (Precision.AlmostEquals(endTime, holdEndTimes[i], 1)) + holdAddition = 0; + + // We give a slight bonus to everything if something is held meanwhile + if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1)) + holdFactor = 1.25; + + // Decay individual strains + individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base); + } + + holdEndTimes[column] = endTime; + + // Increase individual strain in own column + individualStrains[column] += 2.0 * holdFactor; + individualStrain = individualStrains[column]; + + overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base) + (1 + holdAddition) * holdFactor; + + return individualStrain + overallStrain - CurrentStrain; + } + + protected override double GetPeakStrain(double offset) + => applyDecay(individualStrain, offset - Previous[0].BaseObject.StartTime, individual_decay_base) + + applyDecay(overallStrain, offset - Previous[0].BaseObject.StartTime, overall_decay_base); + + private double applyDecay(double value, double deltaTime, double decayBase) + => value * Math.Pow(decayBase, deltaTime / 1000); + } +} From 306d876d22042d3e6d61b41e3bb534b254e7c276 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:50:11 +0900 Subject: [PATCH 096/350] Replicate stable's unstable sort --- .../Difficulty/ManiaDifficultyCalculator.cs | 14 +- .../MathUtils/LegacySortHelper.cs | 164 ++++++++++++++++++ 2 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 7dd1755742..a3694f354b 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -10,10 +11,12 @@ using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mania.Difficulty.Skills; +using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Difficulty @@ -51,10 +54,17 @@ namespace osu.Game.Rulesets.Mania.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - for (int i = 1; i < beatmap.HitObjects.Count; i++) - yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); + var sortedObjects = beatmap.HitObjects.ToArray(); + + LegacySortHelper.Sort(sortedObjects, Comparer.Create((a, b) => (int)Math.Round(a.StartTime) - (int)Math.Round(b.StartTime))); + + for (int i = 1; i < sortedObjects.Length; i++) + yield return new ManiaDifficultyHitObject(sortedObjects[i], sortedObjects[i - 1], clockRate); } + // Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required. + protected override IEnumerable SortObjects(IEnumerable input) => input; + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain(((ManiaBeatmap)beatmap).TotalColumns) diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs new file mode 100644 index 0000000000..421cc0ae04 --- /dev/null +++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs @@ -0,0 +1,164 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace osu.Game.Rulesets.Mania.MathUtils +{ + /// + /// Provides access to .NET4.0 unstable sorting methods. + /// + /// + /// Source: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/arraysorthelper.cs + /// + internal static class LegacySortHelper + { + private const int quick_sort_depth_threshold = 32; + + public static void Sort(T[] keys, IComparer comparer) + { + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + + if (keys.Length == 0) + return; + + comparer ??= Comparer.Default; + depthLimitedQuickSort(keys, 0, keys.Length - 1, comparer, quick_sort_depth_threshold); + } + + private static void depthLimitedQuickSort(T[] keys, int left, int right, IComparer comparer, int depthLimit) + { + do + { + if (depthLimit == 0) + { + heapsort(keys, left, right, comparer); + return; + } + + int i = left; + int j = right; + + // pre-sort the low, middle (pivot), and high values in place. + // this improves performance in the face of already sorted data, or + // data that is made up of multiple sorted runs appended together. + int middle = i + ((j - i) >> 1); + swapIfGreater(keys, comparer, i, middle); // swap the low with the mid point + swapIfGreater(keys, comparer, i, j); // swap the low with the high + swapIfGreater(keys, comparer, middle, j); // swap the middle with the high + + T x = keys[middle]; + + do + { + while (comparer.Compare(keys[i], x) < 0) i++; + while (comparer.Compare(x, keys[j]) < 0) j--; + Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) Sort failed - Is your IComparer bogus?"); + if (i > j) break; + + if (i < j) + { + T key = keys[i]; + keys[i] = keys[j]; + keys[j] = key; + } + + i++; + j--; + } while (i <= j); + + // The next iteration of the while loop is to "recursively" sort the larger half of the array and the + // following calls recrusively sort the smaller half. So we subtrack one from depthLimit here so + // both sorts see the new value. + depthLimit--; + + if (j - left <= right - i) + { + if (left < j) depthLimitedQuickSort(keys, left, j, comparer, depthLimit); + left = i; + } + else + { + if (i < right) depthLimitedQuickSort(keys, i, right, comparer, depthLimit); + right = j; + } + } while (left < right); + } + + private static void heapsort(T[] keys, int lo, int hi, IComparer comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + + int n = hi - lo + 1; + + for (int i = n / 2; i >= 1; i = i - 1) + { + downHeap(keys, i, n, lo, comparer); + } + + for (int i = n; i > 1; i = i - 1) + { + swap(keys, lo, lo + i - 1); + downHeap(keys, 1, i - 1, lo, comparer); + } + } + + private static void downHeap(T[] keys, int i, int n, int lo, IComparer comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(lo < keys.Length); + + T d = keys[lo + i - 1]; + + while (i <= n / 2) + { + var child = 2 * i; + + if (child < n && comparer.Compare(keys[lo + child - 1], keys[lo + child]) < 0) + { + child++; + } + + if (!(comparer.Compare(d, keys[lo + child - 1]) < 0)) + break; + + keys[lo + i - 1] = keys[lo + child - 1]; + i = child; + } + + keys[lo + i - 1] = d; + } + + private static void swap(T[] a, int i, int j) + { + if (i != j) + { + T t = a[i]; + a[i] = a[j]; + a[j] = t; + } + } + + private static void swapIfGreater(T[] keys, IComparer comparer, int a, int b) + { + if (a != b) + { + if (comparer.Compare(keys[a], keys[b]) > 0) + { + T key = keys[a]; + keys[a] = keys[b]; + keys[b] = key; + } + } + } + } +} From 65d8530a11008333c8fc3b75c9fbee2a74c3d96b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 22:47:32 +0900 Subject: [PATCH 097/350] Fix tests --- .../Testing/Beatmaps/convert-samples-expected-conversion.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json index fec1360b26..d49ffa01c5 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -10,7 +10,7 @@ ["soft-hitnormal"], ["drum-hitnormal"] ], - "Samples": ["drum-hitnormal"] + "Samples": ["-hitnormal"] }, { "StartTime": 1875.0, "EndTime": 2750.0, @@ -19,7 +19,7 @@ ["soft-hitnormal"], ["drum-hitnormal"] ], - "Samples": ["drum-hitnormal"] + "Samples": ["-hitnormal"] }] }, { "StartTime": 3750.0, From 6e8011a7ee51a0f1a9a6abf528f227595abb4e6e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 9 Oct 2020 17:28:59 +0300 Subject: [PATCH 098/350] Write xmldoc for TestFourVariousResultsOneMiss --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index dd191b03c2..f7144beda7 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -54,6 +54,21 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + /// + /// Test to see that all contribute to score portions in correct amounts. + /// + /// Scoring mode to test + /// HitResult that will be applied to HitObjects + /// HitResult used for accuracy calcualtion + /// Expected score after 3/4 hitobjects have been hit + /// + /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo + /// expectedScore is calcualted using this algorithm for standardised scoring: 1_000_000 * ((75% * score_per_hitobject / max_per_hitobject * 30%) + (50% * 70%)) + /// "75% * score_per_hitobject / max_per_hitobject" is the accuracy we would get for hitting 3/4 hitobjects that award "score_per_hitobject / max_per_hitobject" accuracy each hit + /// + /// expectedScore is calculated using this algorithm for classic scoring: score_per_hitobject / max_per_hitobject * 936 + /// "936" is simplified from "75% * 4 * 300 * (1 + 1/25)" + /// [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] From a279c38af49a57ea7a6b668d37b774c5863335f2 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 9 Oct 2020 17:33:13 +0300 Subject: [PATCH 099/350] Convert all expectedScore values to int --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index f7144beda7..4f4503faea 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Rulesets.Scoring /// Scoring mode to test /// HitResult that will be applied to HitObjects /// HitResult used for accuracy calcualtion - /// Expected score after 3/4 hitobjects have been hit + /// Expected score after 3/4 hitobjects have been hit rounded to nearest integer /// /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo /// expectedScore is calcualted using this algorithm for standardised scoring: 1_000_000 * ((75% * score_per_hitobject / max_per_hitobject * 30%) + (50% * 70%)) @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] - [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 3_350_000 / 7.0)] + [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 3744 / 7.0)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] - public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, double expectedScore) + public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -113,14 +113,14 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor.ApplyResult(judgementResult); } - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); } - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 6_850_000 / 7.0)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 6_400_000 / 7.0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 1950 / 7.0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 1500 / 7.0)] - public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, double expectedScore) + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 214)] + public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable .Repeat(new TestHitObject(HitResult.SmallTickHit), 4) @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Rulesets.Scoring }; scoreProcessor.ApplyResult(lastJudgementResult); - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); } [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] From 20f1eb2b33765d477fdabf04a7f3e287fe2b9170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Oct 2020 13:11:36 +0900 Subject: [PATCH 100/350] Fix windows key blocking applying when window is inactive / when watching a replay Closes #10467. --- osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index 86174ceb90..07af009b81 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -5,24 +5,24 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; +using osu.Game; using osu.Game.Configuration; namespace osu.Desktop.Windows { public class GameplayWinKeyBlocker : Component { - private Bindable allowScreenSuspension; private Bindable disableWinKey; + private Bindable localUserPlaying; - private GameHost host; + [Resolved] + private GameHost host { get; set; } - [BackgroundDependencyLoader] - private void load(GameHost host, OsuConfigManager config) + [BackgroundDependencyLoader(true)] + private void load(OsuGame game, OsuConfigManager config) { - this.host = host; - - allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy(); - allowScreenSuspension.BindValueChanged(_ => updateBlocking()); + localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); + localUserPlaying.BindValueChanged(_ => updateBlocking()); disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); disableWinKey.BindValueChanged(_ => updateBlocking(), true); @@ -30,7 +30,7 @@ namespace osu.Desktop.Windows private void updateBlocking() { - bool shouldDisable = disableWinKey.Value && !allowScreenSuspension.Value; + bool shouldDisable = disableWinKey.Value && localUserPlaying.Value; if (shouldDisable) host.InputThread.Scheduler.Add(WindowsKey.Disable); From 09e350d14d069def409a82a1b56128bdd79fb17d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Oct 2020 13:28:24 +0900 Subject: [PATCH 101/350] Remove canBNull specification --- osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index 07af009b81..efc3f21149 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -18,7 +18,7 @@ namespace osu.Desktop.Windows [Resolved] private GameHost host { get; set; } - [BackgroundDependencyLoader(true)] + [BackgroundDependencyLoader] private void load(OsuGame game, OsuConfigManager config) { localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); From df9c4bf0a554dd9df255a84546b05aac2b605e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 13:01:52 +0200 Subject: [PATCH 102/350] Improve test xmldoc slightly --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 40e6589ac4..b83b97a539 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -55,19 +55,24 @@ namespace osu.Game.Tests.Rulesets.Scoring } /// - /// Test to see that all contribute to score portions in correct amounts. + /// Test to see that all s contribute to score portions in correct amounts. /// - /// Scoring mode to test - /// HitResult that will be applied to HitObjects - /// HitResult used for accuracy calcualtion - /// Expected score after 3/4 hitobjects have been hit rounded to nearest integer + /// Scoring mode to test. + /// The that will be applied to selected hit objects. + /// The maximum achievable. + /// Expected score after all objects have been judged, rounded to the nearest integer. /// - /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo - /// expectedScore is calcualted using this algorithm for standardised scoring: 1_000_000 * ((75% * score_per_hitobject / max_per_hitobject * 30%) + (50% * 70%)) - /// "75% * score_per_hitobject / max_per_hitobject" is the accuracy we would get for hitting 3/4 hitobjects that award "score_per_hitobject / max_per_hitobject" accuracy each hit - /// - /// expectedScore is calculated using this algorithm for classic scoring: score_per_hitobject / max_per_hitobject * 936 - /// "936" is simplified from "75% * 4 * 300 * (1 + 1/25)" + /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and exactly 50% max combo. + /// + /// For standardised scoring, is calculated using the following formula: + /// 1_000_000 * ((3 * / 4 * ) * 30% + 50% * 70%) + /// + /// + /// For classic scoring, is calculated using the following formula: + /// / * 936 + /// where 936 is simplified from: + /// 75% * 4 * 300 * (1 + 1/25) + /// /// [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] From 73c238fae3d395f65f4089c2c68179ea828c4a77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Oct 2020 21:34:01 +0900 Subject: [PATCH 103/350] Add the ability to search for local beatmaps via online IDs Closes #10470. --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 8 ++++++++ osu.Game/Screens/Select/FilterCriteria.cs | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 3892e02a8f..83e3c84f39 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -58,6 +58,14 @@ namespace osu.Game.Screens.Select.Carousel foreach (var criteriaTerm in criteria.SearchTerms) match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); + + // if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs. + // this should be done after text matching so we can prioritise matching numbers in metadata. + if (!match && criteria.SearchNumber.HasValue) + { + match = (Beatmap.OnlineBeatmapID == criteria.SearchNumber.Value) || + (Beatmap.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value); + } } if (match) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 66f164bca8..f34f8f6505 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -43,6 +43,11 @@ namespace osu.Game.Screens.Select private string searchText; + /// + /// as a number (if it can be parsed as one). + /// + public int? SearchNumber { get; private set; } + public string SearchText { get => searchText; @@ -50,6 +55,11 @@ namespace osu.Game.Screens.Select { searchText = value; SearchTerms = searchText.Split(new[] { ',', ' ', '!' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); + + SearchNumber = null; + + if (SearchTerms.Length == 1 && int.TryParse(SearchTerms[0], out int parsed)) + SearchNumber = parsed; } } From 7bbdd6ab25917834bf04ac46a57424f53b97efa3 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 10 Oct 2020 21:07:17 +0800 Subject: [PATCH 104/350] expose break time bindable --- osu.Game/Screens/Play/Player.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 74714e7e59..6d910e39ed 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -89,6 +89,8 @@ namespace osu.Game.Screens.Play public BreakOverlay BreakOverlay; + public IBindable IsBreakTime => breakTracker?.IsBreakTime; + private BreakTracker breakTracker; private SkipOverlay skipOverlay; From a7c43e17c2a5a72ae358334f49ae46da5fbcb4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 15:41:48 +0200 Subject: [PATCH 105/350] Add test coverage --- .../NonVisual/Filtering/FilterMatchingTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 30686cb947..24a0a662ba 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -197,5 +197,22 @@ namespace osu.Game.Tests.NonVisual.Filtering carouselItem.Filter(criteria); Assert.AreEqual(filtered, carouselItem.Filtered.Value); } + + [TestCase("202010", true)] + [TestCase("20201010", false)] + [TestCase("153", true)] + [TestCase("1535", false)] + public void TestCriteriaMatchingBeatmapIDs(string query, bool filtered) + { + var beatmap = getExampleBeatmap(); + beatmap.OnlineBeatmapID = 20201010; + beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = 1535 }; + + var criteria = new FilterCriteria { SearchText = query }; + var carouselItem = new CarouselBeatmap(beatmap); + carouselItem.Filter(criteria); + + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } } } From f41879ee7c84509bbab4018e7112e08a9a7bfd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 17:54:37 +0200 Subject: [PATCH 106/350] Show current hit circle placement in timeline --- .../Blueprints/HitCircles/HitCirclePlacementBlueprint.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 3dbbdcc5d0..e14d6647d2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -21,6 +21,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles InternalChild = circlePiece = new HitCirclePiece(); } + protected override void LoadComplete() + { + base.LoadComplete(); + BeginPlacement(); + } + protected override void Update() { base.Update(); From 75b26d0cdecd5335c4009009036e0b2c160bde63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 18:08:19 +0200 Subject: [PATCH 107/350] Add failing test cases --- .../Beatmaps/BeatmapDifficultyManagerTest.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs index 0f6d956b3c..7c1ddd757f 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs @@ -28,5 +28,28 @@ namespace osu.Game.Tests.Beatmaps Assert.That(key1, Is.EqualTo(key2)); } + + [TestCase(1.3, DifficultyRating.Easy)] + [TestCase(1.993, DifficultyRating.Easy)] + [TestCase(1.998, DifficultyRating.Normal)] + [TestCase(2.4, DifficultyRating.Normal)] + [TestCase(2.693, DifficultyRating.Normal)] + [TestCase(2.698, DifficultyRating.Hard)] + [TestCase(3.5, DifficultyRating.Hard)] + [TestCase(3.993, DifficultyRating.Hard)] + [TestCase(3.997, DifficultyRating.Insane)] + [TestCase(5.0, DifficultyRating.Insane)] + [TestCase(5.292, DifficultyRating.Insane)] + [TestCase(5.297, DifficultyRating.Expert)] + [TestCase(6.2, DifficultyRating.Expert)] + [TestCase(6.493, DifficultyRating.Expert)] + [TestCase(6.498, DifficultyRating.ExpertPlus)] + [TestCase(8.3, DifficultyRating.ExpertPlus)] + public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket) + { + var actualBracket = BeatmapDifficultyManager.GetDifficultyRating(starRating); + + Assert.AreEqual(expectedBracket, actualBracket); + } } } From 8af78656e456b70f2f0366c8c3f4741147104037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 18:15:52 +0200 Subject: [PATCH 108/350] Add precision tolerance to difficulty rating range checks --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 945a60fb62..8c12ca6f6e 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -14,6 +14,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -124,13 +125,22 @@ namespace osu.Game.Beatmaps /// The that best describes . public static DifficultyRating GetDifficultyRating(double starRating) { - if (starRating < 2.0) return DifficultyRating.Easy; - if (starRating < 2.7) return DifficultyRating.Normal; - if (starRating < 4.0) return DifficultyRating.Hard; - if (starRating < 5.3) return DifficultyRating.Insane; - if (starRating < 6.5) return DifficultyRating.Expert; + if (Precision.AlmostBigger(starRating, 6.5, 0.005)) + return DifficultyRating.ExpertPlus; - return DifficultyRating.ExpertPlus; + if (Precision.AlmostBigger(starRating, 5.3, 0.005)) + return DifficultyRating.Expert; + + if (Precision.AlmostBigger(starRating, 4.0, 0.005)) + return DifficultyRating.Insane; + + if (Precision.AlmostBigger(starRating, 2.7, 0.005)) + return DifficultyRating.Hard; + + if (Precision.AlmostBigger(starRating, 2.0, 0.005)) + return DifficultyRating.Normal; + + return DifficultyRating.Easy; } private CancellationTokenSource trackedUpdateCancellationSource; From 6a52c98a428b5c3c032c5908d863ce4bcfc9ca89 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 11 Oct 2020 06:15:20 +0800 Subject: [PATCH 109/350] make IsBreakTime its own bindable and bind it to BreakTracker on load --- osu.Game/Screens/Play/Player.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6d910e39ed..4f13843503 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -89,7 +89,10 @@ namespace osu.Game.Screens.Play public BreakOverlay BreakOverlay; - public IBindable IsBreakTime => breakTracker?.IsBreakTime; + /// + /// Whether the gameplay is currently in a break. + /// + public readonly BindableBool IsBreakTime = new BindableBool(); private BreakTracker breakTracker; @@ -259,6 +262,7 @@ namespace osu.Game.Screens.Play mod.ApplyToHealthProcessor(HealthProcessor); breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); + IsBreakTime.BindTo((BindableBool)breakTracker.IsBreakTime); } private Drawable createUnderlayComponents() => From 8faa86b048e982eaa75e70abc49308a53000306e Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 10 Oct 2020 18:30:13 -0700 Subject: [PATCH 110/350] Add ability to toggle extended statistics using space or enter --- osu.Game/Screens/Ranking/ResultsScreen.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c48cd238c0..026ce01857 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -10,9 +10,11 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; @@ -22,7 +24,7 @@ using osuTK; namespace osu.Game.Screens.Ranking { - public abstract class ResultsScreen : OsuScreen + public abstract class ResultsScreen : OsuScreen, IKeyBindingHandler { protected const float BACKGROUND_BLUR = 20; private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y; @@ -314,6 +316,22 @@ namespace osu.Game.Screens.Ranking } } + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Select: + statisticsPanel.ToggleVisibility(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } + private class VerticalScrollContainer : OsuScrollContainer { protected override Container Content => content; From 5fcdee6fd8e2fe9631b0564878aa2b63179dc6c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Oct 2020 21:46:55 +0900 Subject: [PATCH 111/350] Remove cast and expose as IBindable --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4f13843503..4dfa609a2e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Play /// /// Whether the gameplay is currently in a break. /// - public readonly BindableBool IsBreakTime = new BindableBool(); + public readonly IBindable IsBreakTime = new BindableBool(); private BreakTracker breakTracker; @@ -261,8 +261,8 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); + IsBreakTime.BindTo(breakTracker.IsBreakTime); breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); - IsBreakTime.BindTo((BindableBool)breakTracker.IsBreakTime); } private Drawable createUnderlayComponents() => From de6fe34361424c9ea1fa15697b90667e0d95b1e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Oct 2020 21:51:48 +0900 Subject: [PATCH 112/350] Bind to local bindable and combine dual bindings --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4dfa609a2e..a2a53b4b75 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -231,7 +231,6 @@ namespace osu.Game.Screens.Play DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); - breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -262,7 +261,7 @@ namespace osu.Game.Screens.Play mod.ApplyToHealthProcessor(HealthProcessor); IsBreakTime.BindTo(breakTracker.IsBreakTime); - breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); + IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } private Drawable createUnderlayComponents() => @@ -360,6 +359,7 @@ namespace osu.Game.Screens.Play private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { + updateGameplayState(); updatePauseOnFocusLostState(); HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; } From e5548a12161afb7bf77c67f3cb68bcd662b61998 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 12 Oct 2020 00:16:18 +0200 Subject: [PATCH 113/350] Move ModSettingsContainer class inside ModSelectOverlay --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 4eb4fc6501..3b158ee98d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -284,7 +284,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new Container + ModSettingsContainer = new CModSettingsContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, @@ -495,5 +495,19 @@ namespace osu.Game.Overlays.Mods } #endregion + + protected class CModSettingsContainer : Container + { + protected override bool OnMouseDown(MouseDownEvent e) + { + return true; + } + + protected override bool OnHover(HoverEvent e) + { + return true; + } + } + } } From ac4290dfb65c19374e66e6c47c7d132c4440dd03 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 15:27:33 +0900 Subject: [PATCH 114/350] Add comment about stable calculation --- .../Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 415201951b..30d33de06e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -57,8 +57,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; SpanCount = repeatsData?.SpanCount() ?? 1; - StartTime = (int)Math.Round(hitObject.StartTime); + + // This matches stable's calculation. EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier); SegmentDuration = (EndTime - StartTime) / SpanCount; From 379971578dbc7cdc80c352ae35a46d0025602784 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 15:28:16 +0900 Subject: [PATCH 115/350] Remove culling notice from HasEffect --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index bb8ae4a66a..4c90b16745 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps.Timing public double Duration => EndTime - StartTime; /// - /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. + /// Whether the break has any effect. /// public bool HasEffect => Duration >= MIN_BREAK_DURATION; From ccf7e2c49a0818c51c669fc620dcfa13edbe083a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 16:31:42 +0900 Subject: [PATCH 116/350] Fallback to default ruleset star rating if conversion fails --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 8c12ca6f6e..c1f4c07833 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -13,10 +13,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; namespace osu.Game.Beatmaps { @@ -238,6 +240,24 @@ namespace osu.Game.Beatmaps return difficultyCache[key] = new StarDifficulty(attributes.StarRating, attributes.MaxCombo); } + catch (BeatmapInvalidForRulesetException e) + { + // Conversion has failed for the given ruleset, so return the difficulty in the beatmap's default ruleset. + + // Ensure the beatmap's default ruleset isn't the one already being converted to. + // This shouldn't happen as it means something went seriously wrong, but if it does an endless loop should be avoided. + if (rulesetInfo.Equals(beatmapInfo.Ruleset)) + { + Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineBeatmapID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); + return difficultyCache[key] = new StarDifficulty(); + } + + // Check the cache first because this is now a different ruleset than the one previously guarded against. + if (tryGetExisting(beatmapInfo, beatmapInfo.Ruleset, Array.Empty(), out var existingDefault, out var existingDefaultKey)) + return existingDefault; + + return computeDifficulty(existingDefaultKey, beatmapInfo, beatmapInfo.Ruleset); + } catch { return difficultyCache[key] = new StarDifficulty(); From e70d2614747fafb47b6d0393117c890d0de176e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 18:03:41 +0900 Subject: [PATCH 117/350] Add failing test --- .../Formats/LegacyBeatmapDecoderTest.cs | 39 +++++++++++++++++++ .../Resources/multi-segment-slider.osu | 8 ++++ 2 files changed, 47 insertions(+) create mode 100644 osu.Game.Tests/Resources/multi-segment-slider.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index dab923d75b..74055ca3ce 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -651,5 +651,44 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsInstanceOf(decoder); } } + + [Test] + public void TestMultiSegmentSliders() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("multi-segment-slider.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + + // Multi-segment + var first = ((IHasPath)decoded.HitObjects[0]).Path; + + Assert.That(first.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(first.ControlPoints[0].Type.Value, Is.EqualTo(PathType.PerfectCurve)); + Assert.That(first.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(161, -244))); + Assert.That(first.ControlPoints[1].Type.Value, Is.EqualTo(null)); + + Assert.That(first.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3))); + Assert.That(first.ControlPoints[2].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(first.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(68, 15))); + Assert.That(first.ControlPoints[3].Type.Value, Is.EqualTo(null)); + Assert.That(first.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(259, -132))); + Assert.That(first.ControlPoints[4].Type.Value, Is.EqualTo(null)); + Assert.That(first.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(92, -107))); + Assert.That(first.ControlPoints[5].Type.Value, Is.EqualTo(null)); + + // Single-segment + var second = ((IHasPath)decoded.HitObjects[1]).Path; + + Assert.That(second.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(second.ControlPoints[0].Type.Value, Is.EqualTo(PathType.PerfectCurve)); + Assert.That(second.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(161, -244))); + Assert.That(second.ControlPoints[1].Type.Value, Is.EqualTo(null)); + Assert.That(second.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3))); + Assert.That(second.ControlPoints[2].Type.Value, Is.EqualTo(null)); + } + } } } diff --git a/osu.Game.Tests/Resources/multi-segment-slider.osu b/osu.Game.Tests/Resources/multi-segment-slider.osu new file mode 100644 index 0000000000..03cddba5e5 --- /dev/null +++ b/osu.Game.Tests/Resources/multi-segment-slider.osu @@ -0,0 +1,8 @@ +osu file format v128 + +[HitObjects] +// Multi-segment +63,301,1000,6,0,P|224:57|B|439:298|131:316|322:169|155:194,1,1040,0|0,0:0|0:0,0:0:0:0: + +// Single-segment +63,301,2000,6,0,P|224:57|439:298,1,1040,0|0,0:0|0:0,0:0:0:0: \ No newline at end of file From 48c0ae40effb05113829dc5c6dffc8ac985ada28 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 18:04:28 +0900 Subject: [PATCH 118/350] Fix multi-segment sliders not parsing correctly --- .../Objects/Legacy/ConvertHitObjectParser.cs | 157 ++++++++++++------ 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index f6adeced96..c5ea26bc20 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -70,53 +70,33 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(LegacyHitObjectType.Slider)) { - PathType pathType = PathType.Catmull; double? length = null; string[] pointSplit = split[5].Split('|'); - int pointCount = 1; + var controlPoints = new List>(); + int startIndex = 0; + int endIndex = 0; + bool first = true; - foreach (var t in pointSplit) + while (++endIndex < pointSplit.Length) { - if (t.Length > 1) - pointCount++; - } - - var points = new Vector2[pointCount]; - - int pointIndex = 1; - - foreach (string t in pointSplit) - { - if (t.Length == 1) - { - switch (t) - { - case @"C": - pathType = PathType.Catmull; - break; - - case @"B": - pathType = PathType.Bezier; - break; - - case @"L": - pathType = PathType.Linear; - break; - - case @"P": - pathType = PathType.PerfectCurve; - break; - } - + // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1). + if (pointSplit[endIndex].Length > 1) continue; - } - string[] temp = t.Split(':'); - points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; + // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. + // The start of the next segment is the index after the type descriptor. + string endPoint = endIndex < pointSplit.Length - 1 ? pointSplit[endIndex + 1] : null; + + controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), endPoint, first, pos)); + startIndex = endIndex; + first = false; } + if (endIndex > startIndex) + controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), null, first, pos)); + int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) @@ -183,7 +163,7 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, mergeControlPoints(controlPoints), length, repeatCount, nodeSamples); } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { @@ -252,8 +232,56 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.Filename = split.Length > 4 ? split[4] : null; } - private PathControlPoint[] convertControlPoints(Vector2[] vertices, PathType type) + private PathType convertPathType(string input) { + switch (input[0]) + { + default: + case 'C': + return PathType.Catmull; + + case 'B': + return PathType.Bezier; + + case 'L': + return PathType.Linear; + + case 'P': + return PathType.PerfectCurve; + } + } + + /// + /// Converts a given point list into a set of s. + /// + /// The point list. + /// Any extra endpoint to consider as part of the points. This will NOT be returned. + /// Whether this is the first point list in the set. If true the returned set will contain a zero point. + /// The positional offset to apply to the control points. + /// The set of points contained by , prepended by an extra zero point if is true. + private Memory convertControlPoints(ReadOnlySpan pointSpan, string endPoint, bool first, Vector2 offset) + { + PathType type = convertPathType(pointSpan[0]); + + int readOffset = first ? 1 : 0; // First control point is zero for the first segment. + int readablePoints = pointSpan.Length - 1; // Total points readable from the base point span. + int endPointLength = endPoint != null ? 1 : 0; // Extra length if an endpoint is given that lies outside the base point span. + + var vertices = new PathControlPoint[readOffset + readablePoints + endPointLength]; + + // Fill any non-read points. + for (int i = 0; i < readOffset; i++) + vertices[i] = new PathControlPoint(); + + // Parse into control points. + for (int i = 1; i < pointSpan.Length; i++) + readPoint(pointSpan[i], offset, out vertices[readOffset + i - 1]); + + // If an endpoint is given, add it now. + if (endPoint != null) + readPoint(endPoint, offset, out vertices[^1]); + + // Edge-case rules. if (type == PathType.PerfectCurve) { if (vertices.Length != 3) @@ -265,29 +293,48 @@ namespace osu.Game.Rulesets.Objects.Legacy } } - var points = new List(vertices.Length) - { - new PathControlPoint - { - Position = { Value = vertices[0] }, - Type = { Value = type } - } - }; + // Set a definite type for the first control point. + vertices[0].Type.Value = type; + // A path can have multiple segments of the same type if there are two sequential control points with the same position. for (int i = 1; i < vertices.Length; i++) { if (vertices[i] == vertices[i - 1]) - { - points[^1].Type.Value = type; - continue; - } - - points.Add(new PathControlPoint { Position = { Value = vertices[i] } }); + vertices[i].Type.Value = type; } - return points.ToArray(); + return vertices.AsMemory().Slice(0, vertices.Length - endPointLength); - static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + static void readPoint(string value, Vector2 startPos, out PathControlPoint point) + { + string[] vertexSplit = value.Split(':'); + + Vector2 pos = new Vector2((int)Parsing.ParseDouble(vertexSplit[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(vertexSplit[1], Parsing.MAX_COORDINATE_VALUE)) - startPos; + point = new PathControlPoint { Position = { Value = pos } }; + } + + static bool isLinear(PathControlPoint[] p) => Precision.AlmostEquals(0, (p[1].Position.Value.Y - p[0].Position.Value.Y) * (p[2].Position.Value.X - p[0].Position.Value.X) + - (p[1].Position.Value.X - p[0].Position.Value.X) * (p[2].Position.Value.Y - p[0].Position.Value.Y)); + } + + private PathControlPoint[] mergeControlPoints(List> controlPointList) + { + int totalCount = 0; + + foreach (var arr in controlPointList) + totalCount += arr.Length; + + var mergedArray = new PathControlPoint[totalCount]; + var mergedArrayMemory = mergedArray.AsMemory(); + int copyIndex = 0; + + foreach (var arr in controlPointList) + { + arr.CopyTo(mergedArrayMemory.Slice(copyIndex)); + copyIndex += arr.Length; + } + + return mergedArray; } /// From 36a8f61d264372e26b9da448c441519fd4a3be31 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 18:58:07 +0900 Subject: [PATCH 119/350] Add failing test for implicit segments --- .../Formats/LegacyBeatmapDecoderTest.cs | 20 +++++++++++++++++++ .../Resources/multi-segment-slider.osu | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 74055ca3ce..58fd6b0448 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -688,6 +688,26 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(second.ControlPoints[1].Type.Value, Is.EqualTo(null)); Assert.That(second.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3))); Assert.That(second.ControlPoints[2].Type.Value, Is.EqualTo(null)); + + // Implicit multi-segment + var third = ((IHasPath)decoded.HitObjects[2]).Path; + + Assert.That(third.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(third.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(third.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(0, 192))); + Assert.That(third.ControlPoints[1].Type.Value, Is.EqualTo(null)); + Assert.That(third.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(224, 192))); + Assert.That(third.ControlPoints[2].Type.Value, Is.EqualTo(null)); + + Assert.That(third.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(224, 0))); + Assert.That(third.ControlPoints[3].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(third.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(224, -192))); + Assert.That(third.ControlPoints[4].Type.Value, Is.EqualTo(null)); + Assert.That(third.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(480, -192))); + Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null)); + Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0))); + Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null)); + } } } diff --git a/osu.Game.Tests/Resources/multi-segment-slider.osu b/osu.Game.Tests/Resources/multi-segment-slider.osu index 03cddba5e5..6eabe640e4 100644 --- a/osu.Game.Tests/Resources/multi-segment-slider.osu +++ b/osu.Game.Tests/Resources/multi-segment-slider.osu @@ -5,4 +5,7 @@ osu file format v128 63,301,1000,6,0,P|224:57|B|439:298|131:316|322:169|155:194,1,1040,0|0,0:0|0:0,0:0:0:0: // Single-segment -63,301,2000,6,0,P|224:57|439:298,1,1040,0|0,0:0|0:0,0:0:0:0: \ No newline at end of file +63,301,2000,6,0,P|224:57|439:298,1,1040,0|0,0:0|0:0,0:0:0:0: + +// Implicit multi-segment +32,192,3000,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 From eb4ef157ca3d1d9059bcc7f8a41690a3fa32773b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 19:16:37 +0900 Subject: [PATCH 120/350] Fix implicit segments not being constructed correctly --- .../Objects/Legacy/ConvertHitObjectParser.cs | 121 ++++++++++++------ 1 file changed, 80 insertions(+), 41 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index c5ea26bc20..22447180ec 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -72,31 +72,6 @@ namespace osu.Game.Rulesets.Objects.Legacy { double? length = null; - string[] pointSplit = split[5].Split('|'); - - var controlPoints = new List>(); - int startIndex = 0; - int endIndex = 0; - bool first = true; - - while (++endIndex < pointSplit.Length) - { - // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1). - if (pointSplit[endIndex].Length > 1) - continue; - - // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. - // The start of the next segment is the index after the type descriptor. - string endPoint = endIndex < pointSplit.Length - 1 ? pointSplit[endIndex + 1] : null; - - controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), endPoint, first, pos)); - startIndex = endIndex; - first = false; - } - - if (endIndex > startIndex) - controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), null, first, pos)); - int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) @@ -163,7 +138,7 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, mergeControlPoints(controlPoints), length, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples); } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { @@ -252,19 +227,71 @@ namespace osu.Game.Rulesets.Objects.Legacy } /// - /// Converts a given point list into a set of s. + /// Converts a given point string into a set of path control points. /// - /// The point list. - /// Any extra endpoint to consider as part of the points. This will NOT be returned. - /// Whether this is the first point list in the set. If true the returned set will contain a zero point. + /// + /// A point string takes the form: X|1:1|2:2|2:2|3:3|Y|1:1|2:2. + /// This has three segments: + /// + /// + /// X: { (1,1), (2,2) } (implicit segment) + /// + /// + /// X: { (2,2), (3,3) } (implicit segment) + /// + /// + /// Y: { (3,3), (1,1), (2, 2) } (explicit segment) + /// + /// + /// + /// The point string. /// The positional offset to apply to the control points. - /// The set of points contained by , prepended by an extra zero point if is true. - private Memory convertControlPoints(ReadOnlySpan pointSpan, string endPoint, bool first, Vector2 offset) + /// All control points in the resultant path. + private PathControlPoint[] convertPathString(string pointString, Vector2 offset) { - PathType type = convertPathType(pointSpan[0]); + // This code takes on the responsibility of handling explicit segments of the path ("X" & "Y" from above). Implicit segments are handled by calls to convertPoints(). + string[] pointSplit = pointString.Split('|'); + + var controlPoints = new List>(); + int startIndex = 0; + int endIndex = 0; + bool first = true; + + while (++endIndex < pointSplit.Length) + { + // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1). + if (pointSplit[endIndex].Length > 1) + continue; + + // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. + // The start of the next segment is the index after the type descriptor. + string endPoint = endIndex < pointSplit.Length - 1 ? pointSplit[endIndex + 1] : null; + + controlPoints.AddRange(convertPoints(pointSplit.AsMemory().Slice(startIndex, endIndex - startIndex), endPoint, first, offset)); + startIndex = endIndex; + first = false; + } + + if (endIndex > startIndex) + controlPoints.AddRange(convertPoints(pointSplit.AsMemory().Slice(startIndex, endIndex - startIndex), null, first, offset)); + + return mergePointsLists(controlPoints); + } + + /// + /// Converts a given point list into a set of path segments. + /// + /// The point list. + /// Any extra endpoint to consider as part of the points. This will NOT be returned. + /// Whether this is the first segment in the set. If true the first of the returned segments will contain a zero point. + /// The positional offset to apply to the control points. + /// The set of points contained by as one or more segments of the path, prepended by an extra zero point if is true. + private IEnumerable> convertPoints(ReadOnlyMemory points, string endPoint, bool first, Vector2 offset) + { + PathType type = convertPathType(points.Span[0]); int readOffset = first ? 1 : 0; // First control point is zero for the first segment. - int readablePoints = pointSpan.Length - 1; // Total points readable from the base point span. + int readablePoints = points.Length - 1; // Total points readable from the base point span. int endPointLength = endPoint != null ? 1 : 0; // Extra length if an endpoint is given that lies outside the base point span. var vertices = new PathControlPoint[readOffset + readablePoints + endPointLength]; @@ -274,8 +301,8 @@ namespace osu.Game.Rulesets.Objects.Legacy vertices[i] = new PathControlPoint(); // Parse into control points. - for (int i = 1; i < pointSpan.Length; i++) - readPoint(pointSpan[i], offset, out vertices[readOffset + i - 1]); + for (int i = 1; i < points.Length; i++) + readPoint(points.Span[i], offset, out vertices[readOffset + i - 1]); // If an endpoint is given, add it now. if (endPoint != null) @@ -297,13 +324,25 @@ namespace osu.Game.Rulesets.Objects.Legacy vertices[0].Type.Value = type; // A path can have multiple segments of the same type if there are two sequential control points with the same position. - for (int i = 1; i < vertices.Length; i++) + // To handle such cases, this code may return multiple path segments with the final control point in each segment having a non-null type. + int startIndex = 0; + int endIndex = 0; + + while (++endIndex < vertices.Length - endPointLength) { - if (vertices[i] == vertices[i - 1]) - vertices[i].Type.Value = type; + if (vertices[endIndex].Position.Value != vertices[endIndex - 1].Position.Value) + continue; + + // Force a type on the last point, and return the current control point set as a segment. + vertices[endIndex - 1].Type.Value = type; + yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex); + + // Skip the current control point - as it's the same as the one that's just been returned. + startIndex = endIndex + 1; } - return vertices.AsMemory().Slice(0, vertices.Length - endPointLength); + if (endIndex > startIndex) + yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex); static void readPoint(string value, Vector2 startPos, out PathControlPoint point) { @@ -317,7 +356,7 @@ namespace osu.Game.Rulesets.Objects.Legacy - (p[1].Position.Value.X - p[0].Position.Value.X) * (p[2].Position.Value.Y - p[0].Position.Value.Y)); } - private PathControlPoint[] mergeControlPoints(List> controlPointList) + private PathControlPoint[] mergePointsLists(List> controlPointList) { int totalCount = 0; From 372761a46ff3fdd8693f4646d43542dfe21e4af3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 19:22:34 +0900 Subject: [PATCH 121/350] More/better commenting --- .../Objects/Legacy/ConvertHitObjectParser.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 22447180ec..7dcbc52cea 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -304,11 +304,11 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 1; i < points.Length; i++) readPoint(points.Span[i], offset, out vertices[readOffset + i - 1]); - // If an endpoint is given, add it now. + // If an endpoint is given, add it to the end. if (endPoint != null) readPoint(endPoint, offset, out vertices[^1]); - // Edge-case rules. + // Edge-case rules (to match stable). if (type == PathType.PerfectCurve) { if (vertices.Length != 3) @@ -320,11 +320,15 @@ namespace osu.Game.Rulesets.Objects.Legacy } } - // Set a definite type for the first control point. + // The first control point must have a definite type. vertices[0].Type.Value = type; - // A path can have multiple segments of the same type if there are two sequential control points with the same position. + // A path can have multiple implicit segments of the same type if there are two sequential control points with the same position. // To handle such cases, this code may return multiple path segments with the final control point in each segment having a non-null type. + // For the point string X|1:1|2:2|2:2|3:3, this code returns the segments: + // X: { (1,1), (2, 2) } + // X: { (3, 3) } + // Note: (2, 2) is not returned in the second segments, as it is implicit in the path. int startIndex = 0; int endIndex = 0; From 58194b4a31169f1cb65b4a6a742078e2040a2d40 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 19:36:35 +0900 Subject: [PATCH 122/350] Fix incorrect blank lines --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 58fd6b0448..b6e1af57fd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -707,7 +707,6 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null)); Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0))); Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null)); - } } } From 8768891b12a34c0473f460dea604ddb3d1747d2d Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 12 Oct 2020 14:41:05 +0200 Subject: [PATCH 123/350] Add testing for clicking mods through customisation menu --- .../UserInterface/TestSceneModSettings.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index c5ce3751ef..d4c8b850d3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -18,10 +18,11 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneModSettings : OsuTestScene + public class TestSceneModSettings : OsuManualInputManagerTestScene { private TestModSelectOverlay modSelect; @@ -29,6 +30,8 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2(); + private readonly Mod testCustomisableMenuCoveredMod = new TestModCustomisable1(); + [SetUp] public void SetUp() => Schedule(() => { @@ -95,6 +98,30 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, copy.SpeedChange.Value)); } + [Test] + public void TestCustomisationMenuNoClickthrough() + { + + createModSelect(); + openModSelect(); + + AddStep("change mod settings menu width to full screen", () => modSelect.SetModSettingsWidth(1.0f)); + AddStep("select cm2", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); + AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.Alpha == 1); + AddStep("hover over mod behind settings menu", () => InputManager.MoveMouseTo(modSelect.GetModButton(testCustomisableMenuCoveredMod))); + AddAssert("Mod is not considered hovered over", () => !modSelect.GetModButton(testCustomisableMenuCoveredMod).IsHovered); + AddStep("left click mod", () => InputManager.Click(MouseButton.Left)); + AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); + AddStep("right click mod", () => InputManager.Click(MouseButton.Right)); + AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); + } + + + + private void clickPosition(int x, int y) { + //Move cursor to coordinates + //Click coordinates + } private void createModSelect() { AddStep("create mod select", () => @@ -124,6 +151,19 @@ namespace osu.Game.Tests.Visual.UserInterface public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + + public ModButton GetModButton(Mod mod) + { + return ModSectionsContainer.Children.Single(s => s.ModType == mod.Type). + ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + } + + public float SetModSettingsWidth(float NewWidth) + { + float oldWidth = ModSettingsContainer.Width; + ModSettingsContainer.Width = NewWidth; + return oldWidth; + } } public class TestRulesetInfo : RulesetInfo From 7df9282727c6997ccccc0df7ee55e930ffb674fe Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 12 Oct 2020 15:58:34 +0200 Subject: [PATCH 124/350] CodeAnalysis fixes --- .../UserInterface/TestSceneModSettings.cs | 22 ++++++------------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 1 - 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index d4c8b850d3..a31e244ca5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -101,7 +101,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestCustomisationMenuNoClickthrough() { - createModSelect(); openModSelect(); @@ -116,12 +115,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); } - - - private void clickPosition(int x, int y) { - //Move cursor to coordinates - //Click coordinates - } private void createModSelect() { AddStep("create mod select", () => @@ -148,20 +141,19 @@ namespace osu.Game.Tests.Visual.UserInterface public bool ButtonsLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); - public void SelectMod(Mod mod) => - ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) - .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); - public ModButton GetModButton(Mod mod) { - return ModSectionsContainer.Children.Single(s => s.ModType == mod.Type). - ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + return ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) + .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } - public float SetModSettingsWidth(float NewWidth) + public void SelectMod(Mod mod) => + GetModButton(mod).SelectNext(1); + + public float SetModSettingsWidth(float newWidth) { float oldWidth = ModSettingsContainer.Width; - ModSettingsContainer.Width = NewWidth; + ModSettingsContainer.Width = newWidth; return oldWidth; } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3b158ee98d..37541358b8 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -508,6 +508,5 @@ namespace osu.Game.Overlays.Mods return true; } } - } } From 3224aa7a695828835a8aa00556614c50ecf53e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Oct 2020 17:31:46 +0200 Subject: [PATCH 125/350] Clarify test math even further --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index b83b97a539..30b47d9bd7 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Rulesets.Scoring /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and exactly 50% max combo. /// /// For standardised scoring, is calculated using the following formula: - /// 1_000_000 * ((3 * / 4 * ) * 30% + 50% * 70%) + /// 1_000_000 * (((3 * ) / (4 * )) * 30% + 50% * 70%) /// /// /// For classic scoring, is calculated using the following formula: @@ -74,30 +74,30 @@ namespace osu.Game.Tests.Rulesets.Scoring /// 75% * 4 * 300 * (1 + 1/25) /// /// - [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] - [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] - [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] - [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] - [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] - [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] - [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] - [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] - [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] + [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] // (3 * 0) / (4 * 300) * 300_000 + (0 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] // (3 * 50) / (4 * 300) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] // (3 * 100) / (4 * 300) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] // (3 * 200) / (4 * 350) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] // (3 * 300) / (4 * 300) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] // (3 * 350) / (4 * 350) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] // (3 * 0) / (4 * 10) * 300_000 + 700_000 (max combo 0) + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0) + [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] // (((3 * 300) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] // (((3 * 350) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] // (0 * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] // (0 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -121,10 +121,10 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); } - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 214)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 214)] // (((3 * 0 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable From 82a28d4655277a6a061b5d09ce0b33afc1dd4317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Oct 2020 17:34:20 +0200 Subject: [PATCH 126/350] Fix some inaccuracies --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 30b47d9bd7..0ca5101292 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -62,10 +62,10 @@ namespace osu.Game.Tests.Rulesets.Scoring /// The maximum achievable. /// Expected score after all objects have been judged, rounded to the nearest integer. /// - /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and exactly 50% max combo. + /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo. /// /// For standardised scoring, is calculated using the following formula: - /// 1_000_000 * (((3 * ) / (4 * )) * 30% + 50% * 70%) + /// 1_000_000 * (((3 * ) / (4 * )) * 30% + (bestCombo / maxCombo) * 70%) /// /// /// For classic scoring, is calculated using the following formula: From 25d9b1ecd08ba1b467c18b3b2ce3c4f762230697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Oct 2020 17:36:07 +0200 Subject: [PATCH 127/350] Clarify purpose and construction of extra test --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 0ca5101292..9f16312121 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -121,6 +121,12 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); } + /// + /// This test uses a beatmap with four small ticks and one object with the of . + /// Its goal is to ensure that with the of , + /// small ticks contribute to the accuracy portion, but not the combo portion. + /// In contrast, does not have separate combo and accuracy portion (they are multiplied by each other). + /// [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) From 1a85123b890c8630459561c80095bba22e1df7de Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 12 Oct 2020 21:24:42 +0200 Subject: [PATCH 128/350] rename container class to be more descriptive --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 37541358b8..f74f40b9b4 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -284,7 +284,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new CModSettingsContainer + ModSettingsContainer = new MouseInputAbsorbingContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, @@ -496,17 +496,11 @@ namespace osu.Game.Overlays.Mods #endregion - protected class CModSettingsContainer : Container + protected class MouseInputAbsorbingContainer : Container { - protected override bool OnMouseDown(MouseDownEvent e) - { - return true; - } + protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnHover(HoverEvent e) - { - return true; - } + protected override bool OnHover(HoverEvent e) => true; } } } From 41d82e3e8ab5377955e7b978e749de7f52fc82ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:34:11 +0900 Subject: [PATCH 129/350] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3df894fbcc..c3c755ecd7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8b10f0a7f7..e8498129a1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 88abbca73d..4b38d9f22d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From b08e7ce1f52743307e4c78c3feafc298b0c4dfc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:37:24 +0900 Subject: [PATCH 130/350] Update BaseColour specification --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index be3bca3242..9aff4ddf8f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform = new WaveformGraph { RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), + BaseColour = colours.Blue.Opacity(0.2f), LowColour = colours.BlueLighter, MidColour = colours.BlueDark, HighColour = colours.BlueDarker, From a393bbe8f7c905997d5e0c5717609d31848e4b99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 12:37:32 +0900 Subject: [PATCH 131/350] Remove direct drawable storage from carousel models --- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/CarouselBeatmapSet.cs | 2 +- .../Screens/Select/Carousel/CarouselGroup.cs | 18 +-------------- .../Screens/Select/Carousel/CarouselItem.cs | 23 ++----------------- 4 files changed, 5 insertions(+), 40 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 83e3c84f39..6a18e2d6a3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Select.Carousel State.Value = CarouselItemState.Collapsed; } - protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this); + public override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this); public override void Filter(FilterCriteria criteria) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 92ccfde14b..75d40a6b03 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel .ForEach(AddChild); } - protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); + public override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); protected override CarouselItem GetNextToSelect() { diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index aa48d1a04e..b85e868b89 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select.Carousel /// public class CarouselGroup : CarouselItem { - protected override DrawableCarouselItem CreateDrawableRepresentation() => null; + public override DrawableCarouselItem CreateDrawableRepresentation() => null; public IReadOnlyList Children => InternalChildren; @@ -23,22 +23,6 @@ namespace osu.Game.Screens.Select.Carousel /// private ulong currentChildID; - public override List Drawables - { - get - { - var drawables = base.Drawables; - - // if we are explicitly not present, don't ever present children. - // without this check, children drawables can potentially be presented without their group header. - if (DrawableRepresentation.Value?.IsPresent == false) return drawables; - - foreach (var c in InternalChildren) - drawables.AddRange(c.Drawables); - return drawables; - } - } - public virtual void RemoveChild(CarouselItem i) { InternalChildren.Remove(i); diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 79c1a4cb6b..555c32c041 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -1,8 +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; -using System.Collections.Generic; using osu.Framework.Bindables; namespace osu.Game.Screens.Select.Carousel @@ -18,23 +16,8 @@ namespace osu.Game.Screens.Select.Carousel /// public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered.Value; - public virtual List Drawables - { - get - { - var items = new List(); - - var self = DrawableRepresentation.Value; - if (self?.IsPresent == true) items.Add(self); - - return items; - } - } - protected CarouselItem() { - DrawableRepresentation = new Lazy(CreateDrawableRepresentation); - Filtered.ValueChanged += filtered => { if (filtered.NewValue && State.Value == CarouselItemState.Selected) @@ -42,17 +25,15 @@ namespace osu.Game.Screens.Select.Carousel }; } - protected readonly Lazy DrawableRepresentation; - /// /// Used as a default sort method for s of differing types. /// internal ulong ChildID; /// - /// Create a fresh drawable version of this item. If you wish to consume the current representation, use instead. + /// Create a fresh drawable version of this item. /// - protected abstract DrawableCarouselItem CreateDrawableRepresentation(); + public abstract DrawableCarouselItem CreateDrawableRepresentation(); public virtual void Filter(FilterCriteria criteria) { From 9193f5b0ba5b7208818898612e4fb2cefaeef379 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 12:37:41 +0900 Subject: [PATCH 132/350] Expose panel height from non-drawable models --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 ++ osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 ++ osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 75d40a6b03..44b8e72d51 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -12,6 +12,8 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { + public float TotalHeight => DrawableCarouselBeatmapSet.HEIGHT + BeatmapSet.Beatmaps.Count * DrawableCarouselBeatmap.HEIGHT; + public IEnumerable Beatmaps => InternalChildren.OfType(); public BeatmapSetInfo BeatmapSet; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 10745fe3c1..c8f9507b91 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu { + public const float HEIGHT = MAX_HEIGHT; + private readonly BeatmapInfo beatmap; private Sprite background; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 3c8ac69dd2..8332093a3c 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasContextMenu { + public const float HEIGHT = MAX_HEIGHT; + private Action restoreHiddenRequested; private Action viewDetails; From 3143224e5b5b51f939c6130dd026de24bb3e96db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 14:23:18 +0900 Subject: [PATCH 133/350] Refactor how drawable carousel items are constructed --- .../SongSelect/TestSceneBeatmapCarousel.cs | 29 +-- osu.Game/Screens/Select/BeatmapCarousel.cs | 187 ++++++++---------- .../Select/Carousel/CarouselBeatmap.cs | 2 + .../Select/Carousel/CarouselBeatmapSet.cs | 15 +- .../Screens/Select/Carousel/CarouselItem.cs | 2 + .../Carousel/DrawableCarouselBeatmap.cs | 6 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 43 ++++ .../Select/Carousel/DrawableCarouselItem.cs | 35 ++-- 8 files changed, 177 insertions(+), 142 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 3aff390a47..680928b331 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -709,19 +709,20 @@ namespace osu.Game.Tests.Visual.SongSelect private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null) { - createCarousel(carouselAdjust); - - if (beatmapSets == null) - { - beatmapSets = new List(); - - for (int i = 1; i <= set_count; i++) - beatmapSets.Add(createTestBeatmapSet(i)); - } - bool changed = false; - AddStep($"Load {(beatmapSets.Count > 0 ? beatmapSets.Count.ToString() : "some")} beatmaps", () => + + createCarousel(c => { + carouselAdjust?.Invoke(c); + + if (beatmapSets == null) + { + beatmapSets = new List(); + + for (int i = 1; i <= set_count; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + } + carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria()); carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; @@ -807,7 +808,7 @@ namespace osu.Game.Tests.Visual.SongSelect private bool selectedBeatmapVisible() { - var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected); + var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected); if (currentlySelected == null) return true; @@ -908,10 +909,10 @@ namespace osu.Game.Tests.Visual.SongSelect private class TestBeatmapCarousel : BeatmapCarousel { - public new List Items => base.Items; - public bool PendingFilterTask => PendingFilter != null; + public IEnumerable Items => InternalChildren.OfType(); + protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty(); } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5f6f859d66..ef533a1456 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -96,9 +96,6 @@ namespace osu.Game.Screens.Select beatmapSets.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); - // preload drawables as the ctor overhead is quite high currently. - _ = newRoot.Drawables; - root = newRoot; if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; @@ -119,6 +116,8 @@ namespace osu.Game.Screens.Select } private readonly List yPositions = new List(); + private readonly List visibleItems = new List(); + private readonly Cached itemsCache = new Cached(); private readonly Cached scrollPositionCache = new Cached(); @@ -130,8 +129,6 @@ namespace osu.Game.Screens.Select private readonly List previouslyVisitedRandomSets = new List(); private readonly Stack randomSelectedBeatmaps = new Stack(); - protected List Items = new List(); - private CarouselRoot root; private IBindable> itemUpdated; @@ -178,7 +175,8 @@ namespace osu.Game.Screens.Select itemRestored = beatmaps.BeatmapRestored.GetBoundCopy(); itemRestored.BindValueChanged(beatmapRestored); - loadBeatmapSets(GetLoadableBeatmaps()); + if (!beatmapSets.Any()) + loadBeatmapSets(GetLoadableBeatmaps()); } protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.AllButFiles); @@ -558,71 +556,78 @@ namespace osu.Game.Screens.Select { base.Update(); + //todo: this should only refresh items, not everything here if (!itemsCache.IsValid) + { updateItems(); - // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); + // Remove all items that should no longer be on-screen + scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); - // Find index range of all items that should be on-screen - Trace.Assert(Items.Count == yPositions.Count); + // Find index range of all items that should be on-screen + int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); + if (firstIndex < 0) firstIndex = ~firstIndex; + int lastIndex = yPositions.BinarySearch(visibleBottomBound); + if (lastIndex < 0) lastIndex = ~lastIndex; - int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); - if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(visibleBottomBound); - if (lastIndex < 0) lastIndex = ~lastIndex; + scrollableContent.Clear(); - int notVisibleCount = 0; - - // Add those items within the previously found index range that should be displayed. - for (int i = firstIndex; i < lastIndex; ++i) - { - DrawableCarouselItem item = Items[i]; - - if (!item.Item.Visible) + // Add those items within the previously found index range that should be displayed. + for (int i = firstIndex; i < lastIndex; ++i) { - if (!item.IsPresent) - notVisibleCount++; - continue; - } + DrawableCarouselItem item = visibleItems[i].CreateDrawableRepresentation(); - float depth = i + (item is DrawableCarouselBeatmapSet ? -Items.Count : 0); + item.Y = yPositions[i]; + item.Depth = i; - // Only add if we're not already part of the content. - if (!scrollableContent.Contains(item)) - { - // Makes sure headers are always _below_ items, - // and depth flows downward. - item.Depth = depth; + scrollableContent.Add(item); - switch (item.LoadState) + // if (!item.Item.Visible) + // { + // if (!item.IsPresent) + // notVisibleCount++; + // continue; + // } + + // Only add if we're not already part of the content. + /* + if (!scrollableContent.Contains(item)) { - case LoadState.NotLoaded: - LoadComponentAsync(item); - break; + // Makes sure headers are always _below_ items, + // and depth flows downward. + item.Depth = depth; - case LoadState.Loading: - break; + switch (item.LoadState) + { + case LoadState.NotLoaded: + LoadComponentAsync(item); + break; - default: - scrollableContent.Add(item); - break; + case LoadState.Loading: + break; + + default: + scrollableContent.Add(item); + break; + } } - } - else - { - scrollableContent.ChangeChildDepth(item, depth); + else + { + scrollableContent.ChangeChildDepth(item, depth); + } + */ } } - // this is not actually useful right now, but once we have groups may well be. - if (notVisibleCount > 50) - itemsCache.Invalidate(); - // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). foreach (DrawableCarouselItem p in scrollableContent.Children) + { updateItem(p); + + // foreach (var pChild in p.ChildItems) + // updateItem(pChild, p); + } } protected override void UpdateAfterChildren() @@ -633,15 +638,6 @@ namespace osu.Game.Screens.Select updateScrollPosition(); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - // aggressively dispose "off-screen" items to reduce GC pressure. - foreach (var i in Items) - i.Dispose(); - } - private void beatmapRemoved(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) @@ -704,69 +700,39 @@ namespace osu.Game.Screens.Select /// The Y position of the currently selected item. private void updateItems() { - Items = root.Drawables.ToList(); - yPositions.Clear(); + visibleItems.Clear(); float currentY = visibleHalfHeight; - DrawableCarouselBeatmapSet lastSet = null; scrollTarget = null; - foreach (DrawableCarouselItem d in Items) + foreach (CarouselItem item in root.Children) { - if (d.IsPresent) + if (item.Filtered.Value) + continue; + + switch (item) { - switch (d) + case CarouselBeatmapSet set: { - case DrawableCarouselBeatmapSet set: - { - lastSet = set; + visibleItems.Add(set); + yPositions.Add(currentY); + //lastSet = set; - set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); - set.MoveToY(currentY, 750, Easing.OutExpo); - break; - } - - case DrawableCarouselBeatmap beatmap: - { - if (beatmap.Item.State.Value == CarouselItemState.Selected) - // scroll position at currentY makes the set panel appear at the very top of the carousel's screen space - // move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas) - // then reapply the top semi-transparent area (because carousel's screen space starts below it) - // and finally add half of the panel's own height to achieve vertical centering of the panel itself - scrollTarget = currentY - visibleHalfHeight + BleedTop + beatmap.DrawHeight / 2; - - void performMove(float y, float? startY = null) - { - if (startY != null) beatmap.MoveTo(new Vector2(0, startY.Value)); - beatmap.MoveToX(beatmap.Item.State.Value == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); - beatmap.MoveToY(y, 750, Easing.OutExpo); - } - - Debug.Assert(lastSet != null); - - float? setY = null; - if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override. - setY = lastSet.Y + lastSet.DrawHeight + 5; - - if (d.IsLoaded) - performMove(currentY, setY); - else - { - float y = currentY; - d.OnLoadComplete += _ => performMove(y, setY); - } - - break; - } + // TODO: move this logic to DCBS too. + // set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + // set.MoveToY(currentY, 750, Easing.OutExpo); + currentY += set.TotalHeight; + break; } + + default: + continue; + // + // break; + // } } - - yPositions.Add(currentY); - - if (d.Item.Visible) - currentY += d.DrawHeight + 5; } currentY += visibleHalfHeight; @@ -869,6 +835,7 @@ namespace osu.Game.Screens.Select /// public bool UserScrolling { get; private set; } + // ReSharper disable once OptionalParameterHierarchyMismatch fuck off rider protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { UserScrolling = true; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 6a18e2d6a3..dce4028f17 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -10,6 +10,8 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmap : CarouselItem { + public override float TotalHeight => DrawableCarouselBeatmap.HEIGHT; + public readonly BeatmapInfo Beatmap; public CarouselBeatmap(BeatmapInfo beatmap) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 44b8e72d51..e34710f71d 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -12,7 +12,20 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { - public float TotalHeight => DrawableCarouselBeatmapSet.HEIGHT + BeatmapSet.Beatmaps.Count * DrawableCarouselBeatmap.HEIGHT; + public override float TotalHeight + { + get + { + switch (State.Value) + { + case CarouselItemState.Selected: + return DrawableCarouselBeatmapSet.HEIGHT + Children.Count * DrawableCarouselBeatmap.HEIGHT; + + default: + return DrawableCarouselBeatmapSet.HEIGHT; + } + } + } public IEnumerable Beatmaps => InternalChildren.OfType(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 555c32c041..004d9779ef 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -7,6 +7,8 @@ namespace osu.Game.Screens.Select.Carousel { public abstract class CarouselItem { + public virtual float TotalHeight => 0; + public readonly BindableBool Filtered = new BindableBool(); public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index c8f9507b91..7d69251265 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu { - public const float HEIGHT = MAX_HEIGHT; + public const float HEIGHT = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmap; @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select.Carousel : base(panel) { beatmap = panel.Beatmap; - Height *= 0.60f; + Height = HEIGHT; } [BackgroundDependencyLoader(true)] @@ -170,6 +170,8 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); + BorderContainer.MoveToX(Item.State.Value == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + background.Colour = ColourInfo.GradientVertical( new Color4(20, 43, 51, 255), new Color4(40, 86, 102, 255)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8332093a3c..aaa925c6f8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -43,8 +43,13 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } + public override IEnumerable ChildItems => beatmapContainer?.Children ?? base.ChildItems; + private readonly BeatmapSetInfo beatmapSet; + private Container beatmapContainer; + private Bindable beatmapSetState; + public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) : base(set) { @@ -119,6 +124,44 @@ namespace osu.Game.Screens.Select.Carousel } } }; + + // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. + AddRangeInternal(new Drawable[] + { + beatmapContainer = new Container + { + X = 50, + Y = MAX_HEIGHT, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }); + + beatmapSetState = Item.State.GetBoundCopy(); + beatmapSetState.BindValueChanged(setSelected, true); + } + + private void setSelected(ValueChangedEvent obj) + { + switch (obj.NewValue) + { + default: + beatmapContainer.Clear(); + break; + + case CarouselItemState.Selected: + + float yPos = 0; + + foreach (var item in ((CarouselBeatmapSet)Item).Beatmaps.Select(b => b.CreateDrawableRepresentation()).OfType()) + { + item.Y = yPos; + beatmapContainer.Add(item); + yPos += item.Item.TotalHeight; + } + + break; + } } private const int maximum_difficulty_icons = 18; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 121491d6ca..c0405b373d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -27,10 +29,13 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselItem Item; - private Container nestedContainer; - private Container borderContainer; + public virtual IEnumerable ChildItems => Enumerable.Empty(); - private Box hoverLayer; + private readonly Container nestedContainer; + + protected readonly Container BorderContainer; + + private readonly Box hoverLayer; protected override Container Content => nestedContainer; @@ -41,14 +46,8 @@ namespace osu.Game.Screens.Select.Carousel Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; Alpha = 0; - } - private SampleChannel sampleHover; - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { - InternalChild = borderContainer = new Container + InternalChild = BorderContainer = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -68,7 +67,13 @@ namespace osu.Game.Screens.Select.Carousel }, } }; + } + private SampleChannel sampleHover; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); hoverLayer.Colour = colours.Blue.Opacity(0.1f); } @@ -87,7 +92,7 @@ namespace osu.Game.Screens.Select.Carousel base.OnHoverLost(e); } - public void SetMultiplicativeAlpha(float alpha) => borderContainer.Alpha = alpha; + public void SetMultiplicativeAlpha(float alpha) => BorderContainer.Alpha = alpha; protected override void LoadComplete() { @@ -123,8 +128,8 @@ namespace osu.Game.Screens.Select.Carousel { Item.State.Value = CarouselItemState.Selected; - borderContainer.BorderThickness = 2.5f; - borderContainer.EdgeEffect = new EdgeEffectParameters + BorderContainer.BorderThickness = 2.5f; + BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), @@ -137,8 +142,8 @@ namespace osu.Game.Screens.Select.Carousel { Item.State.Value = CarouselItemState.NotSelected; - borderContainer.BorderThickness = 0; - borderContainer.EdgeEffect = new EdgeEffectParameters + BorderContainer.BorderThickness = 0; + BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Offset = new Vector2(1), From f17d661c1a1e1aa67865cb09e5e9e492cfbaa448 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 14:46:51 +0900 Subject: [PATCH 134/350] Add basic range-based invalidation --- osu.Game/Screens/Select/BeatmapCarousel.cs | 70 +++++++--------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ef533a1456..94ba0caefb 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -552,70 +552,44 @@ namespace osu.Game.Screens.Select #endregion + private (int first, int last) displayedRange; + protected override void Update() { base.Update(); + bool revalidateItems = !itemsCache.IsValid; + //todo: this should only refresh items, not everything here - if (!itemsCache.IsValid) - { + if (revalidateItems) updateItems(); - // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); + // Remove all items that should no longer be on-screen + scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); - // Find index range of all items that should be on-screen - int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); - if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(visibleBottomBound); - if (lastIndex < 0) lastIndex = ~lastIndex; + // Find index range of all items that should be on-screen + int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); + if (firstIndex < 0) firstIndex = ~firstIndex; + int lastIndex = yPositions.BinarySearch(visibleBottomBound); + if (lastIndex < 0) lastIndex = ~lastIndex; - scrollableContent.Clear(); + if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) + { + displayedRange = (firstIndex, lastIndex); // Add those items within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) { - DrawableCarouselItem item = visibleItems[i].CreateDrawableRepresentation(); + var panel = scrollableContent.FirstOrDefault(c => c.Item == visibleItems[i]); - item.Y = yPositions[i]; - item.Depth = i; - - scrollableContent.Add(item); - - // if (!item.Item.Visible) - // { - // if (!item.IsPresent) - // notVisibleCount++; - // continue; - // } - - // Only add if we're not already part of the content. - /* - if (!scrollableContent.Contains(item)) + if (panel == null) { - // Makes sure headers are always _below_ items, - // and depth flows downward. - item.Depth = depth; - - switch (item.LoadState) - { - case LoadState.NotLoaded: - LoadComponentAsync(item); - break; - - case LoadState.Loading: - break; - - default: - scrollableContent.Add(item); - break; - } + panel = visibleItems[i].CreateDrawableRepresentation(); + scrollableContent.Add(panel); } - else - { - scrollableContent.ChangeChildDepth(item, depth); - } - */ + + panel.Y = yPositions[i]; + scrollableContent.ChangeChildDepth(panel, i); } } From 0a978c6131352794a60ea3c00db373c59d0144af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 15:36:03 +0900 Subject: [PATCH 135/350] Add basic pooling setup --- osu.Game/Screens/Select/BeatmapCarousel.cs | 17 ++++-- .../Select/Carousel/CarouselBeatmapSet.cs | 2 - .../Carousel/DrawableCarouselBeatmap.cs | 6 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 55 ++++++++++-------- .../Select/Carousel/DrawableCarouselItem.cs | 58 +++++++++++++++---- 5 files changed, 95 insertions(+), 43 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 94ba0caefb..f5b524e57c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -16,6 +16,7 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Threading; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -136,6 +137,8 @@ namespace osu.Game.Screens.Select private IBindable> itemHidden; private IBindable> itemRestored; + private readonly DrawablePool setPool = new DrawablePool(100); + public BeatmapCarousel() { root = new CarouselRoot(this); @@ -146,9 +149,13 @@ namespace osu.Game.Screens.Select { Masking = false, RelativeSizeAxes = Axes.Both, - Child = scrollableContent = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, + setPool, + scrollableContent = new Container + { + RelativeSizeAxes = Axes.X, + } } } }; @@ -580,11 +587,13 @@ namespace osu.Game.Screens.Select // Add those items within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) { - var panel = scrollableContent.FirstOrDefault(c => c.Item == visibleItems[i]); + var item = visibleItems[i]; + + var panel = scrollableContent.FirstOrDefault(c => c.Item == item); if (panel == null) { - panel = visibleItems[i].CreateDrawableRepresentation(); + panel = setPool.Get(p => p.Item = item); scrollableContent.Add(panel); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index e34710f71d..15f622f3c4 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -43,8 +43,6 @@ namespace osu.Game.Screens.Select.Carousel .ForEach(AddChild); } - public override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); - protected override CarouselItem GetNextToSelect() { if (LastSelected == null) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 7d69251265..4135960e06 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -60,10 +60,12 @@ namespace osu.Game.Screens.Select.Carousel private CancellationTokenSource starDifficultyCancellationSource; public DrawableCarouselBeatmap(CarouselBeatmap panel) - : base(panel) { beatmap = panel.Beatmap; Height = HEIGHT; + + // todo: temporary + Item = panel; } [BackgroundDependencyLoader(true)] @@ -79,7 +81,7 @@ namespace osu.Game.Screens.Select.Carousel if (manager != null) hideRequested = manager.Hide; - Children = new Drawable[] + Content.Children = new Drawable[] { background = new Box { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index aaa925c6f8..0bfec4f509 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -45,26 +45,40 @@ namespace osu.Game.Screens.Select.Carousel public override IEnumerable ChildItems => beatmapContainer?.Children ?? base.ChildItems; - private readonly BeatmapSetInfo beatmapSet; + private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; private Container beatmapContainer; private Bindable beatmapSetState; - public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) - : base(set) - { - beatmapSet = set.BeatmapSet; - } + [Resolved] + private BeatmapManager manager { get; set; } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay) + private void load(BeatmapSetOverlay beatmapOverlay) { restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; - Children = new Drawable[] + // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. + AddRangeInternal(new Drawable[] + { + beatmapContainer = new Container + { + X = 50, + Y = MAX_HEIGHT, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }); + } + + protected override void UpdateItem() + { + base.UpdateItem(); + + Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => { @@ -125,17 +139,7 @@ namespace osu.Game.Screens.Select.Carousel } }; - // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. - AddRangeInternal(new Drawable[] - { - beatmapContainer = new Container - { - X = 50, - Y = MAX_HEIGHT, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - }); + beatmapContainer.Clear(); beatmapSetState = Item.State.GetBoundCopy(); beatmapSetState.BindValueChanged(setSelected, true); @@ -153,11 +157,16 @@ namespace osu.Game.Screens.Select.Carousel float yPos = 0; - foreach (var item in ((CarouselBeatmapSet)Item).Beatmaps.Select(b => b.CreateDrawableRepresentation()).OfType()) + var carouselBeatmapSet = (CarouselBeatmapSet)Item; + + foreach (var item in carouselBeatmapSet.Children) { - item.Y = yPos; - beatmapContainer.Add(item); - yPos += item.Item.TotalHeight; + var beatmapPanel = item.CreateDrawableRepresentation(); + + beatmapPanel.Y = yPos; + yPos += item.TotalHeight; + + beatmapContainer.Add((DrawableCarouselBeatmap)beatmapPanel); } break; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index c0405b373d..4c4932c22a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -2,14 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; @@ -19,15 +22,32 @@ using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { - public abstract class DrawableCarouselItem : Container + public abstract class DrawableCarouselItem : PoolableDrawable { public const float MAX_HEIGHT = 80; - public override bool RemoveWhenNotAlive => false; + public override bool IsPresent => base.IsPresent || Item?.Visible == true; - public override bool IsPresent => base.IsPresent || Item.Visible; + public CarouselItem Item + { + get => item; + set + { + if (item == value) + return; - public readonly CarouselItem Item; + if (item != null) + { + Item.Filtered.ValueChanged -= onStateChange; + Item.State.ValueChanged -= onStateChange; + } + + item = value; + + if (IsLoaded) + UpdateItem(); + } + } public virtual IEnumerable ChildItems => Enumerable.Empty(); @@ -37,12 +57,10 @@ namespace osu.Game.Screens.Select.Carousel private readonly Box hoverLayer; - protected override Container Content => nestedContainer; + protected Container Content => nestedContainer; - protected DrawableCarouselItem(CarouselItem item) + protected DrawableCarouselItem() { - Item = item; - Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; Alpha = 0; @@ -70,6 +88,7 @@ namespace osu.Game.Screens.Select.Carousel } private SampleChannel sampleHover; + private CarouselItem item; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) @@ -98,14 +117,27 @@ namespace osu.Game.Screens.Select.Carousel { base.LoadComplete(); - ApplyState(); - Item.Filtered.ValueChanged += _ => Schedule(ApplyState); - Item.State.ValueChanged += _ => Schedule(ApplyState); + UpdateItem(); } + protected virtual void UpdateItem() + { + if (item == null) + return; + + ApplyState(); + + Item.Filtered.ValueChanged += onStateChange; + Item.State.ValueChanged += onStateChange; + } + + private void onStateChange(ValueChangedEvent obj) => Schedule(ApplyState); + + private void onStateChange(ValueChangedEvent _) => Schedule(ApplyState); + protected virtual void ApplyState() { - if (!IsLoaded) return; + Debug.Assert(Item != null); switch (Item.State.Value) { @@ -126,6 +158,8 @@ namespace osu.Game.Screens.Select.Carousel protected virtual void Selected() { + Debug.Assert(Item != null); + Item.State.Value = CarouselItemState.Selected; BorderContainer.BorderThickness = 2.5f; From f3b24b9bb5bd9a5fa9cf28e5c190a565a8ce9df3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 15:36:26 +0900 Subject: [PATCH 136/350] Avoid performing eager selection constantly on adding ranges of new children --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/CarouselGroupEagerSelect.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f5b524e57c..a8212f7e59 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select { CarouselRoot newRoot = new CarouselRoot(this); - beatmapSets.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); + newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null)); root = newRoot; if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 262bea9c71..9e8aad4b6f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.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; namespace osu.Game.Screens.Select.Carousel @@ -54,6 +55,14 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } + public void AddChildren(IEnumerable items) + { + foreach (var i in items) + base.AddChild(i); + + attemptSelection(); + } + public override void AddChild(CarouselItem i) { base.AddChild(i); From 580ea62710810d80a92059fced01560b7085ee26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 15:36:36 +0900 Subject: [PATCH 137/350] Temporarily increase test beatmap count for perf testing --- .../SongSelect/TestSceneBeatmapCarousel.cs | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 680928b331..7d850a0d13 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -835,42 +836,29 @@ namespace osu.Game.Tests.Visual.SongSelect Title = $"test set #{id}!", AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) }, - Beatmaps = new List(new[] - { - new BeatmapInfo - { - OnlineBeatmapID = id * 10, - Version = "Normal", - StarDifficulty = 2, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - } - }, - new BeatmapInfo - { - OnlineBeatmapID = id * 10 + 1, - Version = "Hard", - StarDifficulty = 5, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 5, - } - }, - new BeatmapInfo - { - OnlineBeatmapID = id * 10 + 2, - Version = "Insane", - StarDifficulty = 6, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 7, - } - }, - }), + Beatmaps = getBeatmaps(RNG.Next(1, 20)).ToList() }; } + private IEnumerable getBeatmaps(int count) + { + int id = 0; + + for (int i = 0; i < count; i++) + { + yield return new BeatmapInfo + { + OnlineBeatmapID = id++ * 10, + Version = "Normal", + StarDifficulty = RNG.NextSingle() * 10, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = RNG.NextSingle() * 10, + } + }; + } + } + private BeatmapSetInfo createTestBeatmapSetWithManyDifficulties(int id) { var toReturn = new BeatmapSetInfo From 0400b34349dd805750f78a8ac7b326c0a8299b88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 15:55:47 +0900 Subject: [PATCH 138/350] Load more components asynchronously after pool resolution --- osu.Game/Screens/Select/BeatmapCarousel.cs | 1 - .../Carousel/DrawableCarouselBeatmapSet.cs | 129 ++++++++++-------- 2 files changed, 70 insertions(+), 60 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a8212f7e59..00f62aa515 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -15,7 +15,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Threading; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 0bfec4f509..6717788506 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; - private Container beatmapContainer; + private Container beatmapContainer; private Bindable beatmapSetState; [Resolved] @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select.Carousel // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. AddRangeInternal(new Drawable[] { - beatmapContainer = new Container + beatmapContainer = new Container { X = 50, Y = MAX_HEIGHT, @@ -81,62 +81,68 @@ namespace osu.Game.Screens.Select.Carousel Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => - { - var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) - { - RelativeSizeAxes = Axes.Both, - }; - - background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); - - return background; - }, 300, 5000 - ), - new FillFlowContainer { - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { - new OsuSpriteText + RelativeSizeAxes = Axes.Both, + }; + + background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); + + return background; + }, 300, 5000), + new DelayedLoadUnloadWrapper(() => + { + var mainFlow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), - Shadow = true, - }, - new OsuSpriteText - { - Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), - Shadow = true, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] + new OsuSpriteText { - new BeatmapSetOnlineStatusPill + Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + Shadow = true, + }, + new OsuSpriteText + { + Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + Shadow = true, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5 }, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Status = beatmapSet.Status - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(3), - ChildrenEnumerable = getDifficultyIcons(), - }, + new BeatmapSetOnlineStatusPill + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5 }, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapSet.Status + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(3), + ChildrenEnumerable = getDifficultyIcons(), + }, + } } } - } - } + }; + + mainFlow.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); + + return mainFlow; + }, 100, 5000) }; beatmapContainer.Clear(); @@ -155,19 +161,24 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.Selected: - float yPos = 0; - var carouselBeatmapSet = (CarouselBeatmapSet)Item; - foreach (var item in carouselBeatmapSet.Children) + LoadComponentsAsync(carouselBeatmapSet.Children.Select(c => c.CreateDrawableRepresentation()), loaded => { - var beatmapPanel = item.CreateDrawableRepresentation(); + // make sure the pooled target hasn't changed. + if (carouselBeatmapSet != Item) + return; - beatmapPanel.Y = yPos; - yPos += item.TotalHeight; + float yPos = 0; - beatmapContainer.Add((DrawableCarouselBeatmap)beatmapPanel); - } + foreach (var item in loaded) + { + item.Y = yPos; + yPos += item.Item.TotalHeight; + + beatmapContainer.Add(item); + } + }); break; } From ca1f5dcada5affbb485c93c67716c400d8eb4f8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:11:41 +0900 Subject: [PATCH 139/350] Add back panel padding --- 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 00f62aa515..09c7322c58 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -676,6 +676,8 @@ namespace osu.Game.Screens.Select return set; } + private const float panel_padding = 5; + /// /// Computes the target Y positions for every item in the carousel. /// @@ -705,7 +707,7 @@ namespace osu.Game.Screens.Select // TODO: move this logic to DCBS too. // set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); // set.MoveToY(currentY, 750, Easing.OutExpo); - currentY += set.TotalHeight; + currentY += set.TotalHeight + panel_padding; break; } From 954d43ef56a67d01680fd6f551a78f0d3e2ee725 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:13:25 +0900 Subject: [PATCH 140/350] Debounce state application events --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4c4932c22a..4ba827de86 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -125,15 +125,15 @@ namespace osu.Game.Screens.Select.Carousel if (item == null) return; - ApplyState(); + Scheduler.AddOnce(ApplyState); Item.Filtered.ValueChanged += onStateChange; Item.State.ValueChanged += onStateChange; } - private void onStateChange(ValueChangedEvent obj) => Schedule(ApplyState); + private void onStateChange(ValueChangedEvent obj) => Scheduler.AddOnce(ApplyState); - private void onStateChange(ValueChangedEvent _) => Schedule(ApplyState); + private void onStateChange(ValueChangedEvent _) => Scheduler.AddOnce(ApplyState); protected virtual void ApplyState() { From 3cfc0dc82daf6846090eb4b1db81b12e8f8527d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:13:39 +0900 Subject: [PATCH 141/350] Add safeties to beatmap panel loading code --- .../Carousel/DrawableCarouselBeatmapSet.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 6717788506..f59a6a26e6 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -78,6 +79,9 @@ namespace osu.Game.Screens.Select.Carousel { base.UpdateItem(); + beatmapContainer.Clear(); + beatmapSetState?.UnbindAll(); + Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => @@ -145,8 +149,6 @@ namespace osu.Game.Screens.Select.Carousel }, 100, 5000) }; - beatmapContainer.Clear(); - beatmapSetState = Item.State.GetBoundCopy(); beatmapSetState.BindValueChanged(setSelected, true); } @@ -156,14 +158,16 @@ namespace osu.Game.Screens.Select.Carousel switch (obj.NewValue) { default: - beatmapContainer.Clear(); + foreach (var beatmap in beatmapContainer) + beatmap.FadeOut(50).Expire(); break; case CarouselItemState.Selected: var carouselBeatmapSet = (CarouselBeatmapSet)Item; - LoadComponentsAsync(carouselBeatmapSet.Children.Select(c => c.CreateDrawableRepresentation()), loaded => + // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 + LoadComponentsAsync(carouselBeatmapSet.Children.Select(c => c.CreateDrawableRepresentation()).ToArray(), loaded => { // make sure the pooled target hasn't changed. if (carouselBeatmapSet != Item) @@ -175,9 +179,9 @@ namespace osu.Game.Screens.Select.Carousel { item.Y = yPos; yPos += item.Item.TotalHeight; - - beatmapContainer.Add(item); } + + beatmapContainer.ChildrenEnumerable = loaded; }); break; From 5c2f1346658f1a5a8d624da91040ba80db69b7f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:19:10 +0900 Subject: [PATCH 142/350] Add back left offset for selected set --- osu.Game/Screens/Select/BeatmapCarousel.cs | 10 ---------- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 7 ++++--- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 09c7322c58..956b2977bd 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -702,20 +702,10 @@ namespace osu.Game.Screens.Select { visibleItems.Add(set); yPositions.Add(currentY); - //lastSet = set; - // TODO: move this logic to DCBS too. - // set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); - // set.MoveToY(currentY, 750, Easing.OutExpo); currentY += set.TotalHeight + panel_padding; break; } - - default: - continue; - // - // break; - // } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index f59a6a26e6..d92077cf36 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -153,9 +152,11 @@ namespace osu.Game.Screens.Select.Carousel beatmapSetState.BindValueChanged(setSelected, true); } - private void setSelected(ValueChangedEvent obj) + private void setSelected(ValueChangedEvent selected) { - switch (obj.NewValue) + BorderContainer.MoveToX(selected.NewValue == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + + switch (selected.NewValue) { default: foreach (var beatmap in beatmapContainer) From 5c29aa8cce966d27c92963ded66e48878eb9f70e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:19:20 +0900 Subject: [PATCH 143/350] Fix multiple difficulties being expanded at once --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 4135960e06..656d1e39ba 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -172,7 +172,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); - BorderContainer.MoveToX(Item.State.Value == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + BorderContainer.MoveToX(-50, 500, Easing.OutExpo); background.Colour = ColourInfo.GradientVertical( new Color4(20, 43, 51, 255), @@ -185,6 +185,8 @@ namespace osu.Game.Screens.Select.Carousel { base.Deselected(); + BorderContainer.MoveToX(0, 500, Easing.OutExpo); + background.Colour = new Color4(20, 43, 51, 255); triangles.Colour = OsuColour.Gray(0.5f); } From c5a6f4b453f241b9269bc31fe183ae6888e2cf26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:32:29 +0900 Subject: [PATCH 144/350] Fix scroll to selected beatmap --- osu.Game/Screens/Select/BeatmapCarousel.cs | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 956b2977bd..9f5ee22106 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -604,12 +604,7 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). foreach (DrawableCarouselItem p in scrollableContent.Children) - { updateItem(p); - - // foreach (var pChild in p.ChildItems) - // updateItem(pChild, p); - } } protected override void UpdateAfterChildren() @@ -703,6 +698,26 @@ namespace osu.Game.Screens.Select visibleItems.Add(set); yPositions.Add(currentY); + if (item.State.Value == CarouselItemState.Selected) + { + // scroll position at currentY makes the set panel appear at the very top of the carousel's screen space + // move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas) + // then reapply the top semi-transparent area (because carousel's screen space starts below it) + // and finally add half of the panel's own height to achieve vertical centering of the panel itself + scrollTarget = currentY + DrawableCarouselBeatmapSet.HEIGHT - visibleHalfHeight + BleedTop; + + foreach (var b in set.Beatmaps) + { + if (b.State.Value == CarouselItemState.Selected) + { + scrollTarget += b.TotalHeight / 2; + break; + } + + scrollTarget += b.TotalHeight; + } + } + currentY += set.TotalHeight + panel_padding; break; } From 0a144a1388df5ac6849ffc7e4d31bf304c77cff2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 18:50:10 +0900 Subject: [PATCH 145/350] Correctly free panels after use to avoid finalizer disposal of subtree --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index d92077cf36..1cdc496af8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -53,6 +53,12 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private BeatmapManager manager { get; set; } + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + Item = null; + } + [BackgroundDependencyLoader(true)] private void load(BeatmapSetOverlay beatmapOverlay) { @@ -81,6 +87,9 @@ namespace osu.Game.Screens.Select.Carousel beatmapContainer.Clear(); beatmapSetState?.UnbindAll(); + if (Item == null) + return; + Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => From 524419d5e4ebff561ed99d332e666732a6479eaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:02:08 +0900 Subject: [PATCH 146/350] Fix filtered items being considered for height calculation --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 15f622f3c4..7935debac7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel switch (State.Value) { case CarouselItemState.Selected: - return DrawableCarouselBeatmapSet.HEIGHT + Children.Count * DrawableCarouselBeatmap.HEIGHT; + return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; default: return DrawableCarouselBeatmapSet.HEIGHT; From bb03c5d77c15d0b0230e2ce8ef1ec7f67d15a483 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:11:02 +0900 Subject: [PATCH 147/350] Temporarily disable masking temporarily to fix panels disappearing at extents --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9f5ee22106..20a36dbdc5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -571,7 +571,7 @@ namespace osu.Game.Screens.Select updateItems(); // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); + //scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); // Find index range of all items that should be on-screen int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 1cdc496af8..daee7abcc0 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; @@ -31,6 +32,9 @@ namespace osu.Game.Screens.Select.Carousel { public const float HEIGHT = MAX_HEIGHT; + // TODO: don't do this. need to split out the base class' style so our height isn't fixed to the panel display height (and autosize?). + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + private Action restoreHiddenRequested; private Action viewDetails; From 15325f5f510da118d146fa7543b2dff3d4a1406e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:12:00 +0900 Subject: [PATCH 148/350] Base bounds checks on +1 (to avoid worrying about current item heights) --- 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 20a36dbdc5..3c3804389b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -574,11 +574,16 @@ namespace osu.Game.Screens.Select //scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); // Find index range of all items that should be on-screen - int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); + int firstIndex = yPositions.BinarySearch(visibleUpperBound); if (firstIndex < 0) firstIndex = ~firstIndex; int lastIndex = yPositions.BinarySearch(visibleBottomBound); if (lastIndex < 0) lastIndex = ~lastIndex; + // as we can't be 100% sure on the size of individual carousel drawables, + // always play it safe and extend bounds by one. + firstIndex = Math.Max(0, firstIndex - 1); + lastIndex = Math.Min(yPositions.Count - 1, lastIndex + 1); + if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { displayedRange = (firstIndex, lastIndex); From 8847cedf296f633008e3b7d8a38f90d8606f9fb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:55:17 +0900 Subject: [PATCH 149/350] Add initial pass of vertical transforms --- osu.Game/Screens/Select/BeatmapCarousel.cs | 26 +++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3c3804389b..9c49f4bf67 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -566,12 +566,8 @@ namespace osu.Game.Screens.Select bool revalidateItems = !itemsCache.IsValid; - //todo: this should only refresh items, not everything here if (revalidateItems) - updateItems(); - - // Remove all items that should no longer be on-screen - //scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); + updateYPositions(); // Find index range of all items that should be on-screen int firstIndex = yPositions.BinarySearch(visibleUpperBound); @@ -586,6 +582,9 @@ namespace osu.Game.Screens.Select if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { + // Remove all items that should no longer be on-screen + scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); + displayedRange = (firstIndex, lastIndex); // Add those items within the previously found index range that should be displayed. @@ -598,11 +597,22 @@ namespace osu.Game.Screens.Select if (panel == null) { panel = setPool.Get(p => p.Item = item); + panel.Y = yPositions[i]; + panel.Depth = i; + + panel.ClearTransforms(); + scrollableContent.Add(panel); } + else + { + if (panel.IsPresent) + panel.MoveToY(yPositions[i], 800, Easing.OutQuint); + else + panel.Y = yPositions[i]; - panel.Y = yPositions[i]; - scrollableContent.ChangeChildDepth(panel, i); + scrollableContent.ChangeChildDepth(panel, i); + } } } @@ -682,7 +692,7 @@ namespace osu.Game.Screens.Select /// Computes the target Y positions for every item in the carousel. /// /// The Y position of the currently selected item. - private void updateItems() + private void updateYPositions() { yPositions.Clear(); visibleItems.Clear(); From 813ee1972895350f5ea563e5929acc4f5ca52659 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 19:55:33 +0900 Subject: [PATCH 150/350] Use existing event flow for rendering beatmap difficulties --- .../Carousel/DrawableCarouselBeatmapSet.cs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index daee7abcc0..f03b6fff4a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -52,7 +52,6 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; private Container beatmapContainer; - private Bindable beatmapSetState; [Resolved] private BeatmapManager manager { get; set; } @@ -89,7 +88,6 @@ namespace osu.Game.Screens.Select.Carousel base.UpdateItem(); beatmapContainer.Clear(); - beatmapSetState?.UnbindAll(); if (Item == null) return; @@ -160,46 +158,48 @@ namespace osu.Game.Screens.Select.Carousel return mainFlow; }, 100, 5000) }; - - beatmapSetState = Item.State.GetBoundCopy(); - beatmapSetState.BindValueChanged(setSelected, true); } - private void setSelected(ValueChangedEvent selected) + protected override void Deselected() { - BorderContainer.MoveToX(selected.NewValue == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + base.Deselected(); - switch (selected.NewValue) + BorderContainer.MoveToX(0, 500, Easing.OutExpo); + + foreach (var beatmap in beatmapContainer) + beatmap.FadeOut(50).Expire(); + } + + protected override void Selected() + { + base.Selected(); + + BorderContainer.MoveToX(-100, 500, Easing.OutExpo); + + var carouselBeatmapSet = (CarouselBeatmapSet)Item; + + // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 + var visibleBeatmaps = carouselBeatmapSet.Children + .Where(c => c.Visible) + .Select(c => c.CreateDrawableRepresentation()) + .ToArray(); + + LoadComponentsAsync(visibleBeatmaps, loaded => { - default: - foreach (var beatmap in beatmapContainer) - beatmap.FadeOut(50).Expire(); - break; + // make sure the pooled target hasn't changed. + if (carouselBeatmapSet != Item) + return; - case CarouselItemState.Selected: + float yPos = 0; - var carouselBeatmapSet = (CarouselBeatmapSet)Item; + foreach (var item in loaded) + { + item.Y = yPos; + yPos += item.Item.TotalHeight; + } - // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 - LoadComponentsAsync(carouselBeatmapSet.Children.Select(c => c.CreateDrawableRepresentation()).ToArray(), loaded => - { - // make sure the pooled target hasn't changed. - if (carouselBeatmapSet != Item) - return; - - float yPos = 0; - - foreach (var item in loaded) - { - item.Y = yPos; - yPos += item.Item.TotalHeight; - } - - beatmapContainer.ChildrenEnumerable = loaded; - }); - - break; - } + beatmapContainer.ChildrenEnumerable = loaded; + }); } private const int maximum_difficulty_icons = 18; From 82f9ca3de98d1cef76f64a6c67217384e4700460 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 20:02:06 +0900 Subject: [PATCH 151/350] Bind to filter event changes in base drawable item --- .../Screens/Select/Carousel/DrawableCarouselItem.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4ba827de86..8cf63d184c 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -40,6 +40,12 @@ namespace osu.Game.Screens.Select.Carousel { Item.Filtered.ValueChanged -= onStateChange; Item.State.ValueChanged -= onStateChange; + + if (item is CarouselGroup group) + { + foreach (var c in group.Children) + c.Filtered.ValueChanged -= onStateChange; + } } item = value; @@ -129,6 +135,12 @@ namespace osu.Game.Screens.Select.Carousel Item.Filtered.ValueChanged += onStateChange; Item.State.ValueChanged += onStateChange; + + if (Item is CarouselGroup group) + { + foreach (var c in group.Children) + c.Filtered.ValueChanged += onStateChange; + } } private void onStateChange(ValueChangedEvent obj) => Scheduler.AddOnce(ApplyState); From 220c8ba2c47a8f9ffc0df5cca1f1b465088c0838 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 20:02:45 +0900 Subject: [PATCH 152/350] Fix incorrect vertical offsets when difficulties are filtered away --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9c49f4bf67..79ad9c4584 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -723,6 +723,9 @@ namespace osu.Game.Screens.Select foreach (var b in set.Beatmaps) { + if (!b.Visible) + continue; + if (b.State.Value == CarouselItemState.Selected) { scrollTarget += b.TotalHeight / 2; From ce67f6508477ba96726bbb0c92e8397333fb030d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 20:11:10 +0900 Subject: [PATCH 153/350] Fix single results not showing up --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 79ad9c4584..9e295b6f53 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -578,7 +578,7 @@ namespace osu.Game.Screens.Select // as we can't be 100% sure on the size of individual carousel drawables, // always play it safe and extend bounds by one. firstIndex = Math.Max(0, firstIndex - 1); - lastIndex = Math.Min(yPositions.Count - 1, lastIndex + 1); + lastIndex = Math.Min(yPositions.Count, lastIndex + 1); if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { From fd8654cff3c9343a78cdda2749f5f5a26d293846 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Oct 2020 20:26:20 +0900 Subject: [PATCH 154/350] Add back difficulty panel spacing --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 4 +++- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9e295b6f53..04af07846f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -732,7 +732,7 @@ namespace osu.Game.Screens.Select break; } - scrollTarget += b.TotalHeight; + scrollTarget += b.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 7935debac7..f2f8cb9bd9 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel switch (State.Value) { case CarouselItemState.Selected: - return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; + return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * (DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING + DrawableCarouselBeatmap.HEIGHT); default: return DrawableCarouselBeatmapSet.HEIGHT; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 656d1e39ba..23404a6c6e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -31,7 +31,9 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu { - public const float HEIGHT = MAX_HEIGHT * 0.6f; + public const float CAROUSEL_BEATMAP_SPACING = 5; + + public const float HEIGHT = MAX_HEIGHT * 0.6f; // TODO: add once base class is fixed + CAROUSEL_BEATMAP_SPACING; private readonly BeatmapInfo beatmap; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index f03b6fff4a..20e6258433 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -190,12 +190,12 @@ namespace osu.Game.Screens.Select.Carousel if (carouselBeatmapSet != Item) return; - float yPos = 0; + float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; foreach (var item in loaded) { item.Y = yPos; - yPos += item.Item.TotalHeight; + yPos += item.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } beatmapContainer.ChildrenEnumerable = loaded; From 975cd5a84021b5320207e0bfb7ce0d46c6757aa5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 12:47:12 +0900 Subject: [PATCH 155/350] Add back beatmap difficulty appear/disappear movement --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 20e6258433..660d5d5b31 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -167,7 +167,10 @@ namespace osu.Game.Screens.Select.Carousel BorderContainer.MoveToX(0, 500, Easing.OutExpo); foreach (var beatmap in beatmapContainer) - beatmap.FadeOut(50).Expire(); + { + beatmap.MoveToY(0, 800, Easing.OutQuint); + beatmap.FadeOut(80).Expire(); + } } protected override void Selected() @@ -176,6 +179,9 @@ namespace osu.Game.Screens.Select.Carousel BorderContainer.MoveToX(-100, 500, Easing.OutExpo); + // on selection we show our child beatmaps. + // for now this is a simple drawable construction each selection. + // can be improved in the future. var carouselBeatmapSet = (CarouselBeatmapSet)Item; // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 @@ -190,15 +196,15 @@ namespace osu.Game.Screens.Select.Carousel if (carouselBeatmapSet != Item) return; + beatmapContainer.ChildrenEnumerable = loaded; + float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; foreach (var item in loaded) { - item.Y = yPos; + item.MoveToY(yPos, 800, Easing.OutQuint); yPos += item.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } - - beatmapContainer.ChildrenEnumerable = loaded; }); } From 9814e9ba7f098a4dfd78bb6ae3770e20c87592ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 12:50:39 +0900 Subject: [PATCH 156/350] Split classes out to reduce loc burder on DrawableCarouselBeatmapSet --- .../SongSelect/TestScenePlaySongSelect.cs | 22 ++-- .../Carousel/DrawableCarouselBeatmapSet.cs | 117 ------------------ .../Carousel/FilterableDifficultyIcon.cs | 32 +++++ .../FilterableGroupedDifficultyIcon.cs | 38 ++++++ .../Select/Carousel/PanelBackground.cs | 69 +++++++++++ 5 files changed, 150 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs create mode 100644 osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs create mode 100644 osu.Game/Screens/Select/Carousel/PanelBackground.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0299b7a084..cd97ffe9e7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -507,7 +507,7 @@ namespace osu.Game.Tests.Visual.SongSelect var selectedPanel = songSelect.Carousel.ChildrenOfType().First(s => s.Item.State.Value == CarouselItemState.Selected); // special case for converts checked here. - return selectedPanel.ChildrenOfType().All(i => + return selectedPanel.ChildrenOfType().All(i => i.IsFiltered || i.Item.Beatmap.Ruleset.ID == targetRuleset || i.Item.Beatmap.Ruleset.ID == 0); }); @@ -606,10 +606,10 @@ namespace osu.Game.Tests.Visual.SongSelect set = songSelect.Carousel.ChildrenOfType().First(); }); - DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null; + FilterableDifficultyIcon difficultyIcon = null; AddStep("Find an icon", () => { - difficultyIcon = set.ChildrenOfType() + difficultyIcon = set.ChildrenOfType() .First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex()); }); @@ -634,13 +634,13 @@ namespace osu.Game.Tests.Visual.SongSelect })); BeatmapInfo filteredBeatmap = null; - DrawableCarouselBeatmapSet.FilterableDifficultyIcon filteredIcon = null; + FilterableDifficultyIcon filteredIcon = null; AddStep("Get filtered icon", () => { filteredBeatmap = songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First(b => b.BPM < maxBPM); int filteredBeatmapIndex = getBeatmapIndex(filteredBeatmap.BeatmapSet, filteredBeatmap); - filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); + filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); }); AddStep("Click on a filtered difficulty", () => @@ -674,10 +674,10 @@ namespace osu.Game.Tests.Visual.SongSelect return set != null; }); - DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null; + FilterableDifficultyIcon difficultyIcon = null; AddStep("Find an icon for different ruleset", () => { - difficultyIcon = set.ChildrenOfType() + difficultyIcon = set.ChildrenOfType() .First(icon => icon.Item.Beatmap.Ruleset.ID == 3); }); @@ -725,10 +725,10 @@ namespace osu.Game.Tests.Visual.SongSelect return set != null; }); - DrawableCarouselBeatmapSet.FilterableGroupedDifficultyIcon groupIcon = null; + FilterableGroupedDifficultyIcon groupIcon = null; AddStep("Find group icon for different ruleset", () => { - groupIcon = set.ChildrenOfType() + groupIcon = set.ChildrenOfType() .First(icon => icon.Items.First().Beatmap.Ruleset.ID == 3); }); @@ -821,9 +821,9 @@ namespace osu.Game.Tests.Visual.SongSelect private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmap); - private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, DrawableCarouselBeatmapSet.FilterableDifficultyIcon icon) + private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon) { - return set.ChildrenOfType().ToList().FindIndex(i => i == icon); + return set.ChildrenOfType().ToList().FindIndex(i => i == icon); } private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 660d5d5b31..8ab7a054ff 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -5,15 +5,11 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -22,9 +18,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { @@ -285,116 +279,5 @@ namespace osu.Game.Screens.Select.Carousel State = { Value = state } }; } - - private class PanelBackground : BufferedContainer - { - public PanelBackground(WorkingBeatmap working) - { - CacheDrawnFrameBuffer = true; - RedrawOnScale = false; - - Children = new Drawable[] - { - new BeatmapBackgroundSprite(working) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, - new FillFlowContainer - { - Depth = -1, - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle - Shear = new Vector2(0.8f, 0), - Alpha = 0.5f, - Children = new[] - { - // The left half with no gradient applied - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Width = 0.4f, - }, - // Piecewise-linear gradient with 3 segments to make it appear smoother - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), - Width = 0.05f, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), - Width = 0.2f, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), - Width = 0.05f, - }, - } - }, - }; - } - } - - public class FilterableDifficultyIcon : DifficultyIcon - { - private readonly BindableBool filtered = new BindableBool(); - - public bool IsFiltered => filtered.Value; - - public readonly CarouselBeatmap Item; - - public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.Beatmap) - { - filtered.BindTo(item.Filtered); - filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); - filtered.TriggerChange(); - - Item = item; - } - - protected override bool OnClick(ClickEvent e) - { - Item.State.Value = CarouselItemState.Selected; - return true; - } - } - - public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon - { - public readonly List Items; - - public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) - : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) - { - Items = items; - - foreach (var item in items) - item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay)); - - updateFilteredDisplay(); - } - - protected override bool OnClick(ClickEvent e) - { - Items.First().State.Value = CarouselItemState.Selected; - return true; - } - - private void updateFilteredDisplay() - { - // for now, fade the whole group based on the ratio of hidden items. - this.FadeTo(1 - 0.9f * ((float)Items.Count(i => i.Filtered.Value) / Items.Count), 100); - } - } } } diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs new file mode 100644 index 0000000000..591e9fea22 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -0,0 +1,32 @@ +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.Drawables; + +namespace osu.Game.Screens.Select.Carousel +{ + public class FilterableDifficultyIcon : DifficultyIcon + { + private readonly BindableBool filtered = new BindableBool(); + + public bool IsFiltered => filtered.Value; + + public readonly CarouselBeatmap Item; + + public FilterableDifficultyIcon(CarouselBeatmap item) + : base(item.Beatmap) + { + filtered.BindTo(item.Filtered); + filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); + filtered.TriggerChange(); + + Item = item; + } + + protected override bool OnClick(ClickEvent e) + { + Item.State.Value = CarouselItemState.Selected; + return true; + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs new file mode 100644 index 0000000000..73b5781a37 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Rulesets; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select.Carousel +{ + public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon + { + public readonly List Items; + + public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) + : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) + { + Items = items; + + foreach (var item in items) + item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay)); + + updateFilteredDisplay(); + } + + protected override bool OnClick(ClickEvent e) + { + Items.First().State.Value = CarouselItemState.Selected; + return true; + } + + private void updateFilteredDisplay() + { + // for now, fade the whole group based on the ratio of hidden items. + this.FadeTo(1 - 0.9f * ((float)Items.Count(i => i.Filtered.Value) / Items.Count), 100); + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Select/Carousel/PanelBackground.cs b/osu.Game/Screens/Select/Carousel/PanelBackground.cs new file mode 100644 index 0000000000..587aa0a74e --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/PanelBackground.cs @@ -0,0 +1,69 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select.Carousel +{ + public class PanelBackground : BufferedContainer + { + public PanelBackground(WorkingBeatmap working) + { + CacheDrawnFrameBuffer = true; + RedrawOnScale = false; + + Children = new Drawable[] + { + new BeatmapBackgroundSprite(working) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, + new FillFlowContainer + { + Depth = -1, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle + Shear = new Vector2(0.8f, 0), + Alpha = 0.5f, + Children = new[] + { + // The left half with no gradient applied + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Width = 0.4f, + }, + // Piecewise-linear gradient with 3 segments to make it appear smoother + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), + Width = 0.05f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), + Width = 0.2f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), + Width = 0.05f, + }, + } + }, + }; + } + } +} From b92c22ad42af399e43a40bfe6703790a4ce75ff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:11:47 +0900 Subject: [PATCH 157/350] Add logging --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++++ .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 04af07846f..78012e8bfd 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -18,6 +18,7 @@ using osu.Framework.Threading; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Graphics.Containers; @@ -582,6 +583,8 @@ namespace osu.Game.Screens.Select if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { + Logger.Log("revalidation requested"); + // Remove all items that should no longer be on-screen scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); @@ -596,6 +599,7 @@ namespace osu.Game.Screens.Select if (panel == null) { + Logger.Log($"getting panel for {item} from pool"); panel = setPool.Get(p => p.Item = item); panel.Y = yPositions[i]; panel.Depth = i; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8ab7a054ff..fed32b5849 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Collections; @@ -86,10 +87,12 @@ namespace osu.Game.Screens.Select.Carousel if (Item == null) return; + Logger.Log($"updating item {beatmapSet}"); Content.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => { + Logger.Log($"loaded background item {beatmapSet}"); var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, From f6aa448523b5e5fafe2c694d215671c08cd431df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:21:21 +0900 Subject: [PATCH 158/350] Store y positions inside items rather than in a separate array --- osu.Game/Screens/Select/BeatmapCarousel.cs | 54 +++++++++++++------ .../Screens/Select/Carousel/CarouselItem.cs | 10 +++- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 78012e8bfd..87e930fe8a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -116,7 +116,6 @@ namespace osu.Game.Screens.Select }); } - private readonly List yPositions = new List(); private readonly List visibleItems = new List(); private readonly Cached itemsCache = new Cached(); @@ -570,23 +569,15 @@ namespace osu.Game.Screens.Select if (revalidateItems) updateYPositions(); - // Find index range of all items that should be on-screen - int firstIndex = yPositions.BinarySearch(visibleUpperBound); - if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(visibleBottomBound); - if (lastIndex < 0) lastIndex = ~lastIndex; - - // as we can't be 100% sure on the size of individual carousel drawables, - // always play it safe and extend bounds by one. - firstIndex = Math.Max(0, firstIndex - 1); - lastIndex = Math.Min(yPositions.Count, lastIndex + 1); + var (firstIndex, lastIndex) = getDisplayRange(); if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) { Logger.Log("revalidation requested"); // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); + // TODO: figure out a more resilient way of doing this removal. + // scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); displayedRange = (firstIndex, lastIndex); @@ -601,7 +592,7 @@ namespace osu.Game.Screens.Select { Logger.Log($"getting panel for {item} from pool"); panel = setPool.Get(p => p.Item = item); - panel.Y = yPositions[i]; + panel.Y = item.CarouselYPosition; panel.Depth = i; panel.ClearTransforms(); @@ -611,9 +602,9 @@ namespace osu.Game.Screens.Select else { if (panel.IsPresent) - panel.MoveToY(yPositions[i], 800, Easing.OutQuint); + panel.MoveToY(item.CarouselYPosition, 800, Easing.OutQuint); else - panel.Y = yPositions[i]; + panel.Y = item.CarouselYPosition; scrollableContent.ChangeChildDepth(panel, i); } @@ -626,6 +617,22 @@ namespace osu.Game.Screens.Select updateItem(p); } + private (int firstIndex, int lastIndex) getDisplayRange() + { + // Find index range of all items that should be on-screen + // TODO: reduce allocs of CarouselBoundsItem. + int firstIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleUpperBound)); + if (firstIndex < 0) firstIndex = ~firstIndex; + int lastIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleBottomBound)); + if (lastIndex < 0) lastIndex = ~lastIndex; + + // as we can't be 100% sure on the size of individual carousel drawables, + // always play it safe and extend bounds by one. + firstIndex = Math.Max(0, firstIndex - 1); + lastIndex = Math.Min(visibleItems.Count, lastIndex + 1); + return (firstIndex, lastIndex); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -698,7 +705,6 @@ namespace osu.Game.Screens.Select /// The Y position of the currently selected item. private void updateYPositions() { - yPositions.Clear(); visibleItems.Clear(); float currentY = visibleHalfHeight; @@ -715,7 +721,7 @@ namespace osu.Game.Screens.Select case CarouselBeatmapSet set: { visibleItems.Add(set); - yPositions.Add(currentY); + set.CarouselYPosition = currentY; if (item.State.Value == CarouselItemState.Selected) { @@ -815,6 +821,20 @@ namespace osu.Game.Screens.Select p.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } + /// + /// A carousel item strictly used for binary search purposes. + /// + private class CarouselBoundsItem : CarouselItem + { + public CarouselBoundsItem(in float pos) + { + CarouselYPosition = pos; + } + + public override DrawableCarouselItem CreateDrawableRepresentation() => + throw new NotImplementedException(); + } + private class CarouselRoot : CarouselGroupEagerSelect { private readonly BeatmapCarousel carousel; diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 004d9779ef..4bd477412d 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -1,14 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Bindables; namespace osu.Game.Screens.Select.Carousel { - public abstract class CarouselItem + public abstract class CarouselItem : IComparable { public virtual float TotalHeight => 0; + /// + /// An externally defined value used to determine this item's vertical display offset relative to the carousel. + /// + public float CarouselYPosition; + public readonly BindableBool Filtered = new BindableBool(); public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); @@ -42,6 +48,8 @@ namespace osu.Game.Screens.Select.Carousel } public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => ChildID.CompareTo(other.ChildID); + + public int CompareTo(CarouselItem other) => CarouselYPosition.CompareTo(other.CarouselYPosition); } public enum CarouselItemState From 20b54fb904957a208adb2fdbdfb9351f6a3ebe73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:25:45 +0900 Subject: [PATCH 159/350] Move transform clean-up to pooling free call --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 -- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 87e930fe8a..c15652b132 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -595,8 +595,6 @@ namespace osu.Game.Screens.Select panel.Y = item.CarouselYPosition; panel.Depth = i; - panel.ClearTransforms(); - scrollableContent.Add(panel); } else diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fed32b5849..635eb6f375 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -54,7 +54,9 @@ namespace osu.Game.Screens.Select.Carousel protected override void FreeAfterUse() { base.FreeAfterUse(); + Item = null; + ClearTransforms(); } [BackgroundDependencyLoader(true)] From 06e84c8eb3137c451fa0595fb5b55e92cbb9368f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:44:32 +0900 Subject: [PATCH 160/350] Add comments and split out update steps into a more logical flow --- osu.Game/Screens/Select/BeatmapCarousel.cs | 57 ++++++++++++---------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c15652b132..ad2c4c2ca8 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -566,53 +566,56 @@ namespace osu.Game.Screens.Select bool revalidateItems = !itemsCache.IsValid; + // First we iterate over all non-filtered carousel items and populate their + // vertical position data. if (revalidateItems) updateYPositions(); - var (firstIndex, lastIndex) = getDisplayRange(); + // This data is consumed to find the currently displayable range. + // This is the range we want to keep drawables for, and should exceed the visible range slightly to avoid drawable churn. + var newDisplayRange = getDisplayRange(); - if (revalidateItems || firstIndex != displayedRange.first || lastIndex != displayedRange.last) + // If the filtered items or visible range has changed, pooling requirements need to be checked. + // This involves fetching new items from the pool, returning no-longer required items. + if (revalidateItems || newDisplayRange != displayedRange) { Logger.Log("revalidation requested"); - // Remove all items that should no longer be on-screen - // TODO: figure out a more resilient way of doing this removal. - // scrollableContent.RemoveAll(p => p.Y + p.Item.TotalHeight < visibleUpperBound || p.Y > visibleBottomBound); - - displayedRange = (firstIndex, lastIndex); + displayedRange = newDisplayRange; // Add those items within the previously found index range that should be displayed. - for (int i = firstIndex; i < lastIndex; ++i) + for (int i = displayedRange.first; i < displayedRange.last; ++i) { var item = visibleItems[i]; - var panel = scrollableContent.FirstOrDefault(c => c.Item == item); + if (scrollableContent.Any(c => c.Item == item)) continue; - if (panel == null) - { - Logger.Log($"getting panel for {item} from pool"); - panel = setPool.Get(p => p.Item = item); - panel.Y = item.CarouselYPosition; - panel.Depth = i; + Logger.Log($"getting panel for {item} from pool"); + var panel = setPool.Get(p => p.Item = item); + panel.Depth = i; + panel.Y = item.CarouselYPosition; + scrollableContent.Add(panel); + } - scrollableContent.Add(panel); - } - else - { - if (panel.IsPresent) - panel.MoveToY(item.CarouselYPosition, 800, Easing.OutQuint); - else - panel.Y = item.CarouselYPosition; + // Remove any items which are far out of the visible range. + } - scrollableContent.ChangeChildDepth(panel, i); - } + // Finally, if the filtered items have changed, animate drawables to their new locations. + // This is common if a selected/collapsed state has changed. + if (revalidateItems) + { + foreach (DrawableCarouselItem panel in scrollableContent.Children) + { + panel.MoveToY(panel.Item.CarouselYPosition, 800, Easing.OutQuint); } } - // Update externally controlled state of currently visible items - // (e.g. x-offset and opacity). + // Update externally controlled state of currently visible items (e.g. x-offset and opacity). + // This is a per-frame update on all drawable panels. foreach (DrawableCarouselItem p in scrollableContent.Children) + { updateItem(p); + } } private (int firstIndex, int lastIndex) getDisplayRange() From 29983afceffd4f44d91d114b49929cd4b27b584a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 13:51:27 +0900 Subject: [PATCH 161/350] Replace pool/cleanup logic with simplest form possible This will temporarily break panels that go off-screen, as they will disappear immediately --- osu.Game/Screens/Select/BeatmapCarousel.cs | 32 ++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ad2c4c2ca8..56fe28fd14 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -583,21 +583,31 @@ namespace osu.Game.Screens.Select displayedRange = newDisplayRange; - // Add those items within the previously found index range that should be displayed. - for (int i = displayedRange.first; i < displayedRange.last; ++i) + var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); + + foreach (var panel in scrollableContent.Children) { - var item = visibleItems[i]; + if (toDisplay.Remove(panel.Item)) + { + // panel already displayed. + continue; + } - if (scrollableContent.Any(c => c.Item == item)) continue; - - Logger.Log($"getting panel for {item} from pool"); - var panel = setPool.Get(p => p.Item = item); - panel.Depth = i; - panel.Y = item.CarouselYPosition; - scrollableContent.Add(panel); + // panel displayed but not required + scrollableContent.Remove(panel); } - // Remove any items which are far out of the visible range. + // Add those items within the previously found index range that should be displayed. + foreach (var item in toDisplay) + { + Logger.Log($"getting panel for {item} from pool"); + var panel = setPool.Get(p => p.Item = item); + + panel.Depth = item.CarouselYPosition; // todo: i think this is correct + panel.Y = item.CarouselYPosition; + + scrollableContent.Add(panel); + } } // Finally, if the filtered items have changed, animate drawables to their new locations. From 075bf23714fe358b09481a7bab008042e4f3fa8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 14:14:39 +0900 Subject: [PATCH 162/350] Better track off-screen drawables (and return to pool less often) --- osu.Game/Screens/Select/BeatmapCarousel.cs | 24 ++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 56fe28fd14..c8ba98f6be 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -560,6 +560,16 @@ namespace osu.Game.Screens.Select private (int first, int last) displayedRange; + /// + /// Extend the range to retain already loaded pooled drawables. + /// + private const float distance_offscreen_before_unload = 1024; + + /// + /// Extend the range to update positions / retrieve pooled drawables outside of visible range. + /// + private const float distance_offscreen_to_preload = 256; + protected override void Update() { base.Update(); @@ -585,7 +595,7 @@ namespace osu.Game.Screens.Select var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); - foreach (var panel in scrollableContent.Children) + foreach (var panel in scrollableContent.Children.ToArray()) { if (toDisplay.Remove(panel.Item)) { @@ -593,8 +603,10 @@ namespace osu.Game.Screens.Select continue; } - // panel displayed but not required - scrollableContent.Remove(panel); + // panel loaded as drawable but not required by visible range. + // remove but only if too far off-screen + if (panel.Y < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) + scrollableContent.Remove(panel); } // Add those items within the previously found index range that should be displayed. @@ -603,7 +615,7 @@ namespace osu.Game.Screens.Select Logger.Log($"getting panel for {item} from pool"); var panel = setPool.Get(p => p.Item = item); - panel.Depth = item.CarouselYPosition; // todo: i think this is correct + panel.Depth = item.CarouselYPosition; panel.Y = item.CarouselYPosition; scrollableContent.Add(panel); @@ -632,9 +644,9 @@ namespace osu.Game.Screens.Select { // Find index range of all items that should be on-screen // TODO: reduce allocs of CarouselBoundsItem. - int firstIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleUpperBound)); + int firstIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleUpperBound - distance_offscreen_to_preload)); if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleBottomBound)); + int lastIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleBottomBound + distance_offscreen_to_preload)); if (lastIndex < 0) lastIndex = ~lastIndex; // as we can't be 100% sure on the size of individual carousel drawables, From 1b7e3397c6df1e04233d3c37e3c83d9b4aeb90b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 14:23:29 +0900 Subject: [PATCH 163/350] Use expiry to avoid ToArray --- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c8ba98f6be..65bd607f85 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -595,7 +595,7 @@ namespace osu.Game.Screens.Select var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); - foreach (var panel in scrollableContent.Children.ToArray()) + foreach (var panel in scrollableContent.Children) { if (toDisplay.Remove(panel.Item)) { @@ -606,7 +606,11 @@ namespace osu.Game.Screens.Select // panel loaded as drawable but not required by visible range. // remove but only if too far off-screen if (panel.Y < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) - scrollableContent.Remove(panel); + { + // todo: may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). + panel.ClearTransforms(); + panel.Expire(); + } } // Add those items within the previously found index range that should be displayed. From 2aad482545ce8a95fe3de3630278ddaf4d39b048 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 14:37:44 +0900 Subject: [PATCH 164/350] Fix x offsets of difficulties not being updated --- osu.Game/Screens/Select/BeatmapCarousel.cs | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 65bd607f85..775b4ad7b6 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -638,9 +638,15 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items (e.g. x-offset and opacity). // This is a per-frame update on all drawable panels. - foreach (DrawableCarouselItem p in scrollableContent.Children) + foreach (DrawableCarouselItem item in scrollableContent.Children) { - updateItem(p); + updateItem(item); + + if (item is DrawableCarouselBeatmapSet set) + { + foreach (var diff in set.ChildItems) + updateItem(diff, item); + } } } @@ -831,21 +837,20 @@ namespace osu.Game.Screens.Select /// Update a item's x position and multiplicative alpha based on its y position and /// the current scroll position. /// - /// The item to be updated. - private void updateItem(DrawableCarouselItem p) + /// The item to be updated. + /// For nested items, the parent of the item to be updated. + private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null) { - float itemDrawY = p.Position.Y - visibleUpperBound + p.DrawHeight / 2; + Vector2 posInScroll = scrollableContent.ToLocalSpace(item.ScreenSpaceDrawQuad.Centre); + float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); - // Setting the origin position serves as an additive position on top of potential - // local transformation we may want to apply (e.g. when a item gets selected, we - // may want to smoothly transform it leftwards.) - p.OriginPosition = new Vector2(-offsetX(dist, visibleHalfHeight), 0); + item.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can - // layer transformations on top, with a similar reasoning to the previous comment. - p.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); + // layer alpha transformations on top. + item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } /// From cfec4f4fc14833562f464ec79eea0d4ee9f65549 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 15:19:32 +0900 Subject: [PATCH 165/350] Extract header element from base DrawableCarouselItem class --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/CarouselHeader.cs | 108 +++++++++++++++++ .../Carousel/DrawableCarouselBeatmap.cs | 11 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 20 ++-- .../Select/Carousel/DrawableCarouselItem.cs | 113 +++++------------- 5 files changed, 152 insertions(+), 102 deletions(-) create mode 100644 osu.Game/Screens/Select/Carousel/CarouselHeader.cs diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 775b4ad7b6..bcdbc53e26 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -841,7 +841,7 @@ namespace osu.Game.Screens.Select /// For nested items, the parent of the item to be updated. private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null) { - Vector2 posInScroll = scrollableContent.ToLocalSpace(item.ScreenSpaceDrawQuad.Centre); + Vector2 posInScroll = scrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs new file mode 100644 index 0000000000..f59cccd8b6 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select.Carousel +{ + public class CarouselHeader : Container + { + private SampleChannel sampleHover; + + private readonly Box hoverLayer; + + public Container BorderContainer; + + public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); + + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + + public CarouselHeader() + { + RelativeSizeAxes = Axes.X; + Height = DrawableCarouselItem.MAX_HEIGHT; + + InternalChild = BorderContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + BorderColour = new Color4(221, 255, 255, 255), + Children = new Drawable[] + { + Content, + hoverLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); + hoverLayer.Colour = colours.Blue.Opacity(0.1f); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + State.BindValueChanged(updateState, true); + } + + private void updateState(ValueChangedEvent state) + { + switch (state.NewValue) + { + case CarouselItemState.Collapsed: + case CarouselItemState.NotSelected: + BorderContainer.BorderThickness = 0; + BorderContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, Colour = Color4.Black.Opacity(100), + }; + break; + + case CarouselItemState.Selected: + BorderContainer.BorderThickness = 2.5f; + BorderContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), Radius = 20, Roundness = 10, + }; + break; + } + } + + protected override bool OnHover(HoverEvent e) + { + sampleHover?.Play(); + + hoverLayer.FadeIn(100, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverLayer.FadeOut(1000, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 23404a6c6e..b06b60e6c5 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -64,15 +64,14 @@ namespace osu.Game.Screens.Select.Carousel public DrawableCarouselBeatmap(CarouselBeatmap panel) { beatmap = panel.Beatmap; - Height = HEIGHT; - - // todo: temporary Item = panel; } [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { + Header.Height = HEIGHT; + if (songSelect != null) { startRequested = b => songSelect.FinaliseSelection(b); @@ -83,7 +82,7 @@ namespace osu.Game.Screens.Select.Carousel if (manager != null) hideRequested = manager.Hide; - Content.Children = new Drawable[] + Header.Children = new Drawable[] { background = new Box { @@ -174,7 +173,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); - BorderContainer.MoveToX(-50, 500, Easing.OutExpo); + Header.MoveToX(-50, 500, Easing.OutExpo); background.Colour = ColourInfo.GradientVertical( new Color4(20, 43, 51, 255), @@ -187,7 +186,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Deselected(); - BorderContainer.MoveToX(0, 500, Easing.OutExpo); + Header.MoveToX(0, 500, Easing.OutExpo); background.Colour = new Color4(20, 43, 51, 255); triangles.Colour = OsuColour.Gray(0.5f); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 635eb6f375..e1a2ce3962 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -67,16 +67,12 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; - // TODO: temporary. we probably want to *not* inherit DrawableCarouselItem for this class, but only the above header portion. - AddRangeInternal(new Drawable[] + Content.Add(beatmapContainer = new Container { - beatmapContainer = new Container - { - X = 50, - Y = MAX_HEIGHT, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, + X = 50, + Y = MAX_HEIGHT, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, }); } @@ -90,7 +86,7 @@ namespace osu.Game.Screens.Select.Carousel return; Logger.Log($"updating item {beatmapSet}"); - Content.Children = new Drawable[] + Header.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => { @@ -163,7 +159,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Deselected(); - BorderContainer.MoveToX(0, 500, Easing.OutExpo); + MovementContainer.MoveToX(0, 500, Easing.OutExpo); foreach (var beatmap in beatmapContainer) { @@ -176,7 +172,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); - BorderContainer.MoveToX(-100, 500, Easing.OutExpo); + MovementContainer.MoveToX(-100, 500, Easing.OutExpo); // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 8cf63d184c..6a9b60fa57 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -4,21 +4,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Graphics; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { @@ -28,6 +18,14 @@ namespace osu.Game.Screens.Select.Carousel public override bool IsPresent => base.IsPresent || Item?.Visible == true; + public readonly CarouselHeader Header; + + protected readonly Container Content; + + protected readonly Container MovementContainer; + + private CarouselItem item; + public CarouselItem Item { get => item; @@ -38,8 +36,10 @@ namespace osu.Game.Screens.Select.Carousel if (item != null) { - Item.Filtered.ValueChanged -= onStateChange; - Item.State.ValueChanged -= onStateChange; + item.Filtered.ValueChanged -= onStateChange; + item.State.ValueChanged -= onStateChange; + + Header.State.UnbindFrom(item.State); if (item is CarouselGroup group) { @@ -57,67 +57,33 @@ namespace osu.Game.Screens.Select.Carousel public virtual IEnumerable ChildItems => Enumerable.Empty(); - private readonly Container nestedContainer; - - protected readonly Container BorderContainer; - - private readonly Box hoverLayer; - - protected Container Content => nestedContainer; - protected DrawableCarouselItem() { - Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Alpha = 0; - InternalChild = BorderContainer = new Container + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - BorderColour = new Color4(221, 255, 255, 255), - Children = new Drawable[] + MovementContainer = new Container { - nestedContainer = new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - }, - hoverLayer = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingParameters.Additive, - }, - } + Header = new CarouselHeader(), + Content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + }, }; } - private SampleChannel sampleHover; - private CarouselItem item; - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { - sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); - hoverLayer.Colour = colours.Blue.Opacity(0.1f); - } - - protected override bool OnHover(HoverEvent e) - { - sampleHover?.Play(); - - hoverLayer.FadeIn(100, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverLayer.FadeOut(1000, Easing.OutQuint); - base.OnHoverLost(e); - } - - public void SetMultiplicativeAlpha(float alpha) => BorderContainer.Alpha = alpha; + public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha; protected override void LoadComplete() { @@ -136,6 +102,8 @@ namespace osu.Game.Screens.Select.Carousel Item.Filtered.ValueChanged += onStateChange; Item.State.ValueChanged += onStateChange; + Header.State.BindTo(Item.State); + if (Item is CarouselGroup group) { foreach (var c in group.Children) @@ -171,31 +139,10 @@ namespace osu.Game.Screens.Select.Carousel protected virtual void Selected() { Debug.Assert(Item != null); - - Item.State.Value = CarouselItemState.Selected; - - BorderContainer.BorderThickness = 2.5f; - BorderContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = new Color4(130, 204, 255, 150), - Radius = 20, - Roundness = 10, - }; } protected virtual void Deselected() { - Item.State.Value = CarouselItemState.NotSelected; - - BorderContainer.BorderThickness = 0; - BorderContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(1), - Radius = 10, - Colour = Color4.Black.Opacity(100), - }; } protected override bool OnClick(ClickEvent e) From dd8943eb7f5f3816fc2f3b1d218a41166e373b78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 15:19:41 +0900 Subject: [PATCH 166/350] Update test scene to fix crash --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 7d850a0d13..d3ae3a19ea 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -427,7 +427,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = createTestBeatmapSet(i); + var set = createTestBeatmapSet(i, 3); set.Beatmaps[0].StarDifficulty = 3 - i; set.Beatmaps[2].StarDifficulty = 6 + i; sets.Add(set); @@ -822,7 +822,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection is visible", selectedBeatmapVisible); } - private BeatmapSetInfo createTestBeatmapSet(int id) + private BeatmapSetInfo createTestBeatmapSet(int id, int minimumDifficulties = 1) { return new BeatmapSetInfo { @@ -836,7 +836,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = $"test set #{id}!", AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) }, - Beatmaps = getBeatmaps(RNG.Next(1, 20)).ToList() + Beatmaps = getBeatmaps(RNG.Next(minimumDifficulties, 20)).ToList() }; } From c08b5e8d03b83221ac0c56688d42374262f9c6ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 15:23:27 +0900 Subject: [PATCH 167/350] Align beatmap difficulties correctly --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e1a2ce3962..64a990a27d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select.Carousel Content.Add(beatmapContainer = new Container { - X = 50, + X = 100, Y = MAX_HEIGHT, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From 1da49073abd01ec14b84b3e32c9fa353eae7d738 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 15:32:37 +0900 Subject: [PATCH 168/350] Calculate content height automatically --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 1 - osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 64a990a27d..b83915e0c6 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -70,7 +70,6 @@ namespace osu.Game.Screens.Select.Carousel Content.Add(beatmapContainer = new Container { X = 100, - Y = MAX_HEIGHT, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 6a9b60fa57..5bcaffa3cf 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -20,6 +20,9 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselHeader Header; + /// + /// Optional content which sits below the header. + /// protected readonly Container Content; protected readonly Container MovementContainer; @@ -89,6 +92,9 @@ namespace osu.Game.Screens.Select.Carousel { base.LoadComplete(); + // avoid using fill flow for performance reasons. header size doesn't change after load. + Content.Y = Header.Height; + UpdateItem(); } From cecdf14f5363d9590b130526a8e24e8c3bbddefd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 16:04:37 +0900 Subject: [PATCH 169/350] Avoid reconstructing beatmap difficulties that were recently displayed --- .../Carousel/DrawableCarouselBeatmapSet.cs | 78 ++++++++++++------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b83915e0c6..9a822f83e3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -56,6 +56,7 @@ namespace osu.Game.Screens.Select.Carousel base.FreeAfterUse(); Item = null; + ClearTransforms(); } @@ -66,20 +67,14 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; - - Content.Add(beatmapContainer = new Container - { - X = 100, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }); } protected override void UpdateItem() { base.UpdateItem(); - beatmapContainer.Clear(); + Content.Clear(); + beatmapContainer = null; if (Item == null) return; @@ -160,10 +155,13 @@ namespace osu.Game.Screens.Select.Carousel MovementContainer.MoveToX(0, 500, Easing.OutExpo); - foreach (var beatmap in beatmapContainer) + if (beatmapContainer != null) { - beatmap.MoveToY(0, 800, Easing.OutQuint); - beatmap.FadeOut(80).Expire(); + foreach (var beatmap in beatmapContainer) + { + beatmap.MoveToY(0, 800, Easing.OutQuint); + beatmap.FadeOut(80); + } } } @@ -173,33 +171,55 @@ namespace osu.Game.Screens.Select.Carousel MovementContainer.MoveToX(-100, 500, Easing.OutExpo); - // on selection we show our child beatmaps. - // for now this is a simple drawable construction each selection. - // can be improved in the future. - var carouselBeatmapSet = (CarouselBeatmapSet)Item; - - // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 - var visibleBeatmaps = carouselBeatmapSet.Children - .Where(c => c.Visible) - .Select(c => c.CreateDrawableRepresentation()) - .ToArray(); - - LoadComponentsAsync(visibleBeatmaps, loaded => + if (beatmapContainer != null) { - // make sure the pooled target hasn't changed. - if (carouselBeatmapSet != Item) - return; + // if already loaded, we only need to re-animate. + animateBeatmaps(); + } + else + { + // on selection we show our child beatmaps. + // for now this is a simple drawable construction each selection. + // can be improved in the future. + var carouselBeatmapSet = (CarouselBeatmapSet)Item; - beatmapContainer.ChildrenEnumerable = loaded; + // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 + var visibleBeatmaps = carouselBeatmapSet.Children + .Where(c => c.Visible) + .Select(c => c.CreateDrawableRepresentation()) + .ToArray(); + beatmapContainer = new Container + { + X = 100, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ChildrenEnumerable = visibleBeatmaps + }; + + Logger.Log($"loading {visibleBeatmaps.Length} beatmaps for {Item}"); + + LoadComponentAsync(beatmapContainer, loaded => + { + // make sure the pooled target hasn't changed. + if (carouselBeatmapSet != Item) + return; + + Content.Child = loaded; + animateBeatmaps(); + }); + } + + void animateBeatmaps() + { float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; - foreach (var item in loaded) + foreach (var item in beatmapContainer.Children) { item.MoveToY(yPos, 800, Easing.OutQuint); yPos += item.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } - }); + } } private const int maximum_difficulty_icons = 18; From ded09b78cbbda03d95df2c75f17785a02b84c434 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 16:33:37 +0900 Subject: [PATCH 170/350] Avoid usage of AutoSize for DrawableCarouselItems in general --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 +-- .../Screens/Select/Carousel/DrawableCarouselItem.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 9a822f83e3..a6cea5ee53 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -192,8 +192,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapContainer = new Container { X = 100, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, ChildrenEnumerable = visibleBeatmaps }; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 5bcaffa3cf..68825873ff 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -63,7 +63,6 @@ namespace osu.Game.Screens.Select.Carousel protected DrawableCarouselItem() { RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; Alpha = 0; @@ -71,15 +70,13 @@ namespace osu.Game.Screens.Select.Carousel { MovementContainer = new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { Header = new CarouselHeader(), Content = new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, } } }, @@ -123,6 +120,10 @@ namespace osu.Game.Screens.Select.Carousel protected virtual void ApplyState() { + // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. + // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. + Height = Item.TotalHeight; + Debug.Assert(Item != null); switch (Item.State.Value) From b536f571fd7885ef2048b871997d5a012fb2cedb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 16:35:06 +0900 Subject: [PATCH 171/350] Move header height propagation to update for safety --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 68825873ff..bafb338a04 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -89,10 +89,15 @@ namespace osu.Game.Screens.Select.Carousel { base.LoadComplete(); + UpdateItem(); + } + + protected override void Update() + { + base.Update(); + // avoid using fill flow for performance reasons. header size doesn't change after load. Content.Y = Header.Height; - - UpdateItem(); } protected virtual void UpdateItem() From 1f0aa974dd0a0155e0703036f3c2825e76c94e2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:09:54 +0900 Subject: [PATCH 172/350] Fix failing tests --- .../SongSelect/TestSceneBeatmapCarousel.cs | 37 +++++++++++++++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 18 ++++----- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index d3ae3a19ea..8e4e2ac257 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -427,7 +427,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = createTestBeatmapSet(i, 3); + var set = createTestBeatmapSet(i); set.Beatmaps[0].StarDifficulty = 3 - i; set.Beatmaps[2].StarDifficulty = 6 + i; sets.Add(set); @@ -822,7 +822,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection is visible", selectedBeatmapVisible); } - private BeatmapSetInfo createTestBeatmapSet(int id, int minimumDifficulties = 1) + private BeatmapSetInfo createTestBeatmapSet(int id, bool randomDifficultyCount = false) { return new BeatmapSetInfo { @@ -836,7 +836,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = $"test set #{id}!", AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) }, - Beatmaps = getBeatmaps(RNG.Next(minimumDifficulties, 20)).ToList() + Beatmaps = getBeatmaps(randomDifficultyCount ? RNG.Next(1, 20) : 3).ToList() }; } @@ -846,14 +846,22 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < count; i++) { + float diff = (float)i / count * 10; + + string version = "Normal"; + if (diff > 6.6) + version = "Insane"; + else if (diff > 3.3) + version = "Hard"; + yield return new BeatmapInfo { OnlineBeatmapID = id++ * 10, - Version = "Normal", - StarDifficulty = RNG.NextSingle() * 10, + Version = version, + StarDifficulty = diff, BaseDifficulty = new BeatmapDifficulty { - OverallDifficulty = RNG.NextSingle() * 10, + OverallDifficulty = diff, } }; } @@ -899,7 +907,22 @@ namespace osu.Game.Tests.Visual.SongSelect { public bool PendingFilterTask => PendingFilter != null; - public IEnumerable Items => InternalChildren.OfType(); + public IEnumerable Items + { + get + { + foreach (var item in ScrollableContent) + { + yield return item; + + if (item is DrawableCarouselBeatmapSet set) + { + foreach (var difficulty in set.ChildItems) + yield return difficulty; + } + } + } + } protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty(); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index bcdbc53e26..b6ca6a242d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; - scrollableContent.Clear(false); + ScrollableContent.Clear(false); itemsCache.Invalidate(); scrollPositionCache.Invalidate(); @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Select private readonly Cached itemsCache = new Cached(); private readonly Cached scrollPositionCache = new Cached(); - private readonly Container scrollableContent; + protected readonly Container ScrollableContent; public Bindable RightClickScrollingEnabled = new Bindable(); @@ -151,7 +151,7 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { setPool, - scrollableContent = new Container + ScrollableContent = new Container { RelativeSizeAxes = Axes.X, } @@ -595,7 +595,7 @@ namespace osu.Game.Screens.Select var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); - foreach (var panel in scrollableContent.Children) + foreach (var panel in ScrollableContent.Children) { if (toDisplay.Remove(panel.Item)) { @@ -622,7 +622,7 @@ namespace osu.Game.Screens.Select panel.Depth = item.CarouselYPosition; panel.Y = item.CarouselYPosition; - scrollableContent.Add(panel); + ScrollableContent.Add(panel); } } @@ -630,7 +630,7 @@ namespace osu.Game.Screens.Select // This is common if a selected/collapsed state has changed. if (revalidateItems) { - foreach (DrawableCarouselItem panel in scrollableContent.Children) + foreach (DrawableCarouselItem panel in ScrollableContent.Children) { panel.MoveToY(panel.Item.CarouselYPosition, 800, Easing.OutQuint); } @@ -638,7 +638,7 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items (e.g. x-offset and opacity). // This is a per-frame update on all drawable panels. - foreach (DrawableCarouselItem item in scrollableContent.Children) + foreach (DrawableCarouselItem item in ScrollableContent.Children) { updateItem(item); @@ -786,7 +786,7 @@ namespace osu.Game.Screens.Select } currentY += visibleHalfHeight; - scrollableContent.Height = currentY; + ScrollableContent.Height = currentY; if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) { @@ -841,7 +841,7 @@ namespace osu.Game.Screens.Select /// For nested items, the parent of the item to be updated. private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null) { - Vector2 posInScroll = scrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); + Vector2 posInScroll = ScrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); From fdd4d95cdc6cdbb209b2dd99d2794dd45e428a4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:24:41 +0900 Subject: [PATCH 173/350] Fix difficulties being at incorrect vertical positions after filter is applied --- .../Carousel/DrawableCarouselBeatmapSet.cs | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index a6cea5ee53..6c35ebfd97 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -171,29 +171,36 @@ namespace osu.Game.Screens.Select.Carousel MovementContainer.MoveToX(-100, 500, Easing.OutExpo); - if (beatmapContainer != null) + updateBeatmapDifficulties(); + } + + private void updateBeatmapDifficulties() + { + var carouselBeatmapSet = (CarouselBeatmapSet)Item; + + var visibleBeatmaps = carouselBeatmapSet.Children + .Where(c => c.Visible) + .ToArray(); + + // if we are already displaying all the correct beatmaps, only run animation updates. + // note that the displayed beatmaps may change due to the applied filter. + // a future optimisation could add/remove only changed difficulties rather than reinitialise. + if (beatmapContainer != null && visibleBeatmaps.Length == beatmapContainer.Count && visibleBeatmaps.All(b => beatmapContainer.Any(c => c.Item == b))) { - // if already loaded, we only need to re-animate. - animateBeatmaps(); + updateBeatmapYPositions(); } else { // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. // can be improved in the future. - var carouselBeatmapSet = (CarouselBeatmapSet)Item; - - // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 - var visibleBeatmaps = carouselBeatmapSet.Children - .Where(c => c.Visible) - .Select(c => c.CreateDrawableRepresentation()) - .ToArray(); beatmapContainer = new Container { X = 100, RelativeSizeAxes = Axes.Both, - ChildrenEnumerable = visibleBeatmaps + // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 + ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()).ToArray() }; Logger.Log($"loading {visibleBeatmaps.Length} beatmaps for {Item}"); @@ -205,18 +212,18 @@ namespace osu.Game.Screens.Select.Carousel return; Content.Child = loaded; - animateBeatmaps(); + updateBeatmapYPositions(); }); } - void animateBeatmaps() + void updateBeatmapYPositions() { float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; - foreach (var item in beatmapContainer.Children) + foreach (var panel in beatmapContainer.Children) { - item.MoveToY(yPos, 800, Easing.OutQuint); - yPos += item.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; + panel.MoveToY(yPos, 800, Easing.OutQuint); + yPos += panel.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; } } } From 4160feb3da3dd6fc5c3154dd964ca2c8d61f92ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:26:17 +0900 Subject: [PATCH 174/350] Add test specifically for many panels visible --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 8e4e2ac257..239fa9ec4e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -41,6 +41,12 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestManyPanels() + { + loadBeatmaps(count: 5000, randomDifficulties: true); + } + [Test] public void TestKeyRepeat() { @@ -708,7 +714,7 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(true, 15); } - private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null) + private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, bool randomDifficulties = false) { bool changed = false; @@ -720,8 +726,8 @@ namespace osu.Game.Tests.Visual.SongSelect { beatmapSets = new List(); - for (int i = 1; i <= set_count; i++) - beatmapSets.Add(createTestBeatmapSet(i)); + for (int i = 1; i <= (count ?? set_count); i++) + beatmapSets.Add(createTestBeatmapSet(i, randomDifficulties)); } carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria()); From f3b937e358a8489d3559e5972010caa193b4655a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:33:35 +0900 Subject: [PATCH 175/350] Fix masking issues with certain aspect ratio displays --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +++- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b6ca6a242d..bc2494e9b7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -845,7 +845,9 @@ namespace osu.Game.Screens.Select float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); - item.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); + // adjusting the item's overall X position can cause it to become masked away when + // child items (difficulties) are still visible. + item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index b06b60e6c5..a655186986 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Selected(); - Header.MoveToX(-50, 500, Easing.OutExpo); + MovementContainer.MoveToX(-50, 500, Easing.OutExpo); background.Colour = ColourInfo.GradientVertical( new Color4(20, 43, 51, 255), @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Deselected(); - Header.MoveToX(0, 500, Easing.OutExpo); + MovementContainer.MoveToX(0, 500, Easing.OutExpo); background.Colour = new Color4(20, 43, 51, 255); triangles.Colour = OsuColour.Gray(0.5f); From 9b2ebb8f0f07fef7f781edb9cf60a62a80206976 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:45:41 +0900 Subject: [PATCH 176/350] Fix main content DelayedLoadUnloadWrapper not getting a valid size before load --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 6c35ebfd97..b54325cf05 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -100,7 +100,8 @@ namespace osu.Game.Screens.Select.Carousel { Direction = FillDirection.Vertical, Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, - AutoSizeAxes = Axes.Both, + // required to ensure we load as soon as any part of the panel comes on screen + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new OsuSpriteText From d9a6a6b24543ddc7d1d7a259f37723700473376d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:55:56 +0900 Subject: [PATCH 177/350] Split content out into own class --- .../Carousel/DrawableCarouselBeatmapSet.cs | 66 +------------ ...nelBackground.cs => SetPanelBackground.cs} | 4 +- .../Select/Carousel/SetPanelContent.cs | 93 +++++++++++++++++++ 3 files changed, 98 insertions(+), 65 deletions(-) rename osu.Game/Screens/Select/Carousel/{PanelBackground.cs => SetPanelBackground.cs} (95%) create mode 100644 osu.Game/Screens/Select/Carousel/SetPanelContent.cs diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b54325cf05..ad5c4e5e4f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -10,16 +10,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Collections; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osuTK; namespace osu.Game.Screens.Select.Carousel { @@ -85,7 +80,7 @@ namespace osu.Game.Screens.Select.Carousel new DelayedLoadUnloadWrapper(() => { Logger.Log($"loaded background item {beatmapSet}"); - var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + var background = new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, }; @@ -96,52 +91,8 @@ namespace osu.Game.Screens.Select.Carousel }, 300, 5000), new DelayedLoadUnloadWrapper(() => { - var mainFlow = new FillFlowContainer - { - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, - // required to ensure we load as soon as any part of the panel comes on screen - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), - Shadow = true, - }, - new OsuSpriteText - { - Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), - Shadow = true, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] - { - new BeatmapSetOnlineStatusPill - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5 }, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Status = beatmapSet.Status - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(3), - ChildrenEnumerable = getDifficultyIcons(), - }, - } - } - } - }; + // main content split into own class to reduce allocation before load operation triggers. + var mainFlow = new SetPanelContent((CarouselBeatmapSet)Item); mainFlow.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); @@ -229,17 +180,6 @@ namespace osu.Game.Screens.Select.Carousel } } - private const int maximum_difficulty_icons = 18; - - private IEnumerable getDifficultyIcons() - { - var beatmaps = ((CarouselBeatmapSet)Item).Beatmaps.ToList(); - - return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)) - : beatmaps.Select(b => new FilterableDifficultyIcon(b)); - } - public MenuItem[] ContextMenuItems { get diff --git a/osu.Game/Screens/Select/Carousel/PanelBackground.cs b/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs similarity index 95% rename from osu.Game/Screens/Select/Carousel/PanelBackground.cs rename to osu.Game/Screens/Select/Carousel/SetPanelBackground.cs index 587aa0a74e..6af0cbf4ab 100644 --- a/osu.Game/Screens/Select/Carousel/PanelBackground.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs @@ -9,9 +9,9 @@ using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { - public class PanelBackground : BufferedContainer + public class SetPanelBackground : BufferedContainer { - public PanelBackground(WorkingBeatmap working) + public SetPanelBackground(WorkingBeatmap working) { CacheDrawnFrameBuffer = true; RedrawOnScale = false; diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs new file mode 100644 index 0000000000..4e8d27f14d --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Select.Carousel +{ + public class SetPanelContent : CompositeDrawable + { + private readonly CarouselBeatmapSet carouselSet; + + public SetPanelContent(CarouselBeatmapSet carouselSet) + { + this.carouselSet = carouselSet; + + // required to ensure we load as soon as any part of the panel comes on screen + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + var beatmapSet = carouselSet.BeatmapSet; + + InternalChild = new FillFlowContainer + { + // required to ensure we load as soon as any part of the panel comes on screen + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), + Shadow = true, + }, + new OsuSpriteText + { + Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + Shadow = true, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5 }, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapSet.Status + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(3), + ChildrenEnumerable = getDifficultyIcons(), + }, + } + } + } + }; + } + + private const int maximum_difficulty_icons = 18; + + private IEnumerable getDifficultyIcons() + { + var beatmaps = carouselSet.Beatmaps.ToList(); + + return beatmaps.Count > maximum_difficulty_icons + ? (IEnumerable)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)) + : beatmaps.Select(b => new FilterableDifficultyIcon(b)); + } + } +} From b1ddb08a4efefd378d65ec8756e21c597b089030 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 17:57:38 +0900 Subject: [PATCH 178/350] Fix right click context menus appearing in incorrect locations --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index bafb338a04..a66135196a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Events; +using osuTK; namespace osu.Game.Screens.Select.Carousel { @@ -27,6 +28,9 @@ namespace osu.Game.Screens.Select.Carousel protected readonly Container MovementContainer; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + Header.ReceivePositionalInputAt(screenSpacePos); + private CarouselItem item; public CarouselItem Item From 69650c16fc539d66a589054792a1b5524d92a842 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:13:36 +0900 Subject: [PATCH 179/350] Simplify vertical position calculations by including spacing in height definition --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 9 +++++++-- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index bc2494e9b7..955693a67c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -775,7 +775,7 @@ namespace osu.Game.Screens.Select break; } - scrollTarget += b.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; + scrollTarget += b.TotalHeight; } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index f2f8cb9bd9..7935debac7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel switch (State.Value) { case CarouselItemState.Selected: - return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * (DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING + DrawableCarouselBeatmap.HEIGHT); + return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; default: return DrawableCarouselBeatmapSet.HEIGHT; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a655186986..49a370724e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -33,7 +33,12 @@ namespace osu.Game.Screens.Select.Carousel { public const float CAROUSEL_BEATMAP_SPACING = 5; - public const float HEIGHT = MAX_HEIGHT * 0.6f; // TODO: add once base class is fixed + CAROUSEL_BEATMAP_SPACING; + /// + /// The height of a carousel beatmap, including vertical spacing. + /// + public const float HEIGHT = height + CAROUSEL_BEATMAP_SPACING; + + private const float height = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmap; @@ -70,7 +75,7 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { - Header.Height = HEIGHT; + Header.Height = height; if (songSelect != null) { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index ad5c4e5e4f..68fea14757 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Select.Carousel foreach (var panel in beatmapContainer.Children) { panel.MoveToY(yPos, 800, Easing.OutQuint); - yPos += panel.Item.TotalHeight + DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING; + yPos += panel.Item.TotalHeight; } } } From 3d9ea852ecf48cf40f8669665815b8c2bfb18abb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:14:23 +0900 Subject: [PATCH 180/350] Remove masking override (no longer needed as our size is now correct) --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 68fea14757..b80e789429 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -22,9 +21,6 @@ namespace osu.Game.Screens.Select.Carousel { public const float HEIGHT = MAX_HEIGHT; - // TODO: don't do this. need to split out the base class' style so our height isn't fixed to the panel display height (and autosize?). - protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; - private Action restoreHiddenRequested; private Action viewDetails; From 83358d487fb5770877948e671621eada6cc91abb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:18:22 +0900 Subject: [PATCH 181/350] Remove logging --- osu.Game/Screens/Select/BeatmapCarousel.cs | 20 ++++++++----------- .../Carousel/DrawableCarouselBeatmapSet.cs | 5 ----- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 955693a67c..e53beaef0b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1,30 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; -using System.Linq; -using osu.Game.Configuration; -using osuTK.Input; -using osu.Framework.Utils; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; -using osu.Framework.Threading; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; +using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; +using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Select { @@ -589,8 +588,6 @@ namespace osu.Game.Screens.Select // This involves fetching new items from the pool, returning no-longer required items. if (revalidateItems || newDisplayRange != displayedRange) { - Logger.Log("revalidation requested"); - displayedRange = newDisplayRange; var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); @@ -616,7 +613,6 @@ namespace osu.Game.Screens.Select // Add those items within the previously found index range that should be displayed. foreach (var item in toDisplay) { - Logger.Log($"getting panel for {item} from pool"); var panel = setPool.Get(p => p.Item = item); panel.Depth = item.CarouselYPosition; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b80e789429..bf76adc6ac 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics.UserInterface; @@ -70,12 +69,10 @@ namespace osu.Game.Screens.Select.Carousel if (Item == null) return; - Logger.Log($"updating item {beatmapSet}"); Header.Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => { - Logger.Log($"loaded background item {beatmapSet}"); var background = new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, @@ -151,8 +148,6 @@ namespace osu.Game.Screens.Select.Carousel ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()).ToArray() }; - Logger.Log($"loading {visibleBeatmaps.Length} beatmaps for {Item}"); - LoadComponentAsync(beatmapContainer, loaded => { // make sure the pooled target hasn't changed. From 4f4f2225143c425e9ec9f35592adddffa16284f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:28:28 +0900 Subject: [PATCH 182/350] Remove unnecessary fade (already applied by base DrawableCarouselItem) --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index bf76adc6ac..aae4d0df5d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -103,10 +103,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapContainer != null) { foreach (var beatmap in beatmapContainer) - { beatmap.MoveToY(0, 800, Easing.OutQuint); - beatmap.FadeOut(80); - } } } From 40a0ab7aaaf1139fbcf068ad0e7d358772c43118 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:33:31 +0900 Subject: [PATCH 183/350] Avoid allocating CarouselItems for bounds checks --- osu.Game/Screens/Select/BeatmapCarousel.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e53beaef0b..340f30c120 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -646,19 +646,24 @@ namespace osu.Game.Screens.Select } } + private readonly CarouselBoundsItem carouselBoundsItem = new CarouselBoundsItem(); + private (int firstIndex, int lastIndex) getDisplayRange() { // Find index range of all items that should be on-screen - // TODO: reduce allocs of CarouselBoundsItem. - int firstIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleUpperBound - distance_offscreen_to_preload)); + carouselBoundsItem.CarouselYPosition = visibleUpperBound - distance_offscreen_to_preload; + int firstIndex = visibleItems.BinarySearch(carouselBoundsItem); if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = visibleItems.BinarySearch(new CarouselBoundsItem(visibleBottomBound + distance_offscreen_to_preload)); + + carouselBoundsItem.CarouselYPosition = visibleBottomBound + distance_offscreen_to_preload; + int lastIndex = visibleItems.BinarySearch(carouselBoundsItem); if (lastIndex < 0) lastIndex = ~lastIndex; // as we can't be 100% sure on the size of individual carousel drawables, // always play it safe and extend bounds by one. firstIndex = Math.Max(0, firstIndex - 1); lastIndex = Math.Min(visibleItems.Count, lastIndex + 1); + return (firstIndex, lastIndex); } @@ -856,11 +861,6 @@ namespace osu.Game.Screens.Select /// private class CarouselBoundsItem : CarouselItem { - public CarouselBoundsItem(in float pos) - { - CarouselYPosition = pos; - } - public override DrawableCarouselItem CreateDrawableRepresentation() => throw new NotImplementedException(); } From a1801f8ae40f7e65da4305f7ab08dbb91ce0f7f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:33:55 +0900 Subject: [PATCH 184/350] Unmark todo for now --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 340f30c120..dcebabae4d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -604,7 +604,7 @@ namespace osu.Game.Screens.Select // remove but only if too far off-screen if (panel.Y < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) { - // todo: may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). + // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). panel.ClearTransforms(); panel.Expire(); } From 2346644c048b781fa6eae9fb144d038e9ed1ee40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:47:35 +0900 Subject: [PATCH 185/350] Switch DelayedLoadUnloadWrappers to DelayedLoadWrappers Due to pooling usage, there is no time we need to unload. Switching to DelayedLoadWrapper cleans up the code and reduces overhead substantially. --- .../Carousel/DrawableCarouselBeatmapSet.cs | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index aae4d0df5d..42d073976e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -69,29 +69,25 @@ namespace osu.Game.Screens.Select.Carousel if (Item == null) return; + DelayedLoadWrapper background; + DelayedLoadWrapper mainFlow; + Header.Children = new Drawable[] { - new DelayedLoadUnloadWrapper(() => + background = new DelayedLoadWrapper(new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { - var background = new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) - { - RelativeSizeAxes = Axes.Both, - }; - - background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); - - return background; - }, 300, 5000), - new DelayedLoadUnloadWrapper(() => - { - // main content split into own class to reduce allocation before load operation triggers. - var mainFlow = new SetPanelContent((CarouselBeatmapSet)Item); - - mainFlow.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); - - return mainFlow; - }, 100, 5000) + RelativeSizeAxes = Axes.Both, + }, 300), + mainFlow = new DelayedLoadWrapper(new SetPanelContent((CarouselBeatmapSet)Item), 100), }; + + background.DelayedLoadComplete += fadeContentIn; + mainFlow.DelayedLoadComplete += fadeContentIn; + } + + private void fadeContentIn(Drawable d) + { + d.FadeInFromZero(1000, Easing.OutQuint); } protected override void Deselected() From 834b0186f41f64b12a23f70abfdb34bc923d5074 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 18:50:10 +0900 Subject: [PATCH 186/350] Adjust fade duration to be slightly shorter --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 42d073976e..e83c460f15 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -85,10 +85,7 @@ namespace osu.Game.Screens.Select.Carousel mainFlow.DelayedLoadComplete += fadeContentIn; } - private void fadeContentIn(Drawable d) - { - d.FadeInFromZero(1000, Easing.OutQuint); - } + private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); protected override void Deselected() { From 8eca28e8bc3986ed99c78f54fbe6769dca3af6cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:10:35 +0900 Subject: [PATCH 187/350] Add comment about off-screen loading --- osu.Game/Screens/Select/BeatmapCarousel.cs | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index dcebabae4d..21777c5c7c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -74,6 +74,18 @@ namespace osu.Game.Screens.Select public override bool PropagatePositionalInputSubTree => AllowSelection; public override bool PropagateNonPositionalInputSubTree => AllowSelection; + private (int first, int last) displayedRange; + + /// + /// Extend the range to retain already loaded pooled drawables. + /// + private const float distance_offscreen_before_unload = 1024; + + /// + /// Extend the range to update positions / retrieve pooled drawables outside of visible range. + /// + private const float distance_offscreen_to_preload = 512; // todo: adjust this appropriately once we can make set panel contents load while off-screen. + /// /// Whether carousel items have completed asynchronously loaded. /// @@ -557,18 +569,6 @@ namespace osu.Game.Screens.Select #endregion - private (int first, int last) displayedRange; - - /// - /// Extend the range to retain already loaded pooled drawables. - /// - private const float distance_offscreen_before_unload = 1024; - - /// - /// Extend the range to update positions / retrieve pooled drawables outside of visible range. - /// - private const float distance_offscreen_to_preload = 256; - protected override void Update() { base.Update(); From 37daefc2b59da55abe3fcc9a6fc62cf7deeee623 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:12:33 +0900 Subject: [PATCH 188/350] Remove outdated comment --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index a66135196a..df8d5cd565 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -99,8 +99,6 @@ namespace osu.Game.Screens.Select.Carousel protected override void Update() { base.Update(); - - // avoid using fill flow for performance reasons. header size doesn't change after load. Content.Y = Header.Height; } From 5d11db7753212439c219b7c60863eb4bfb6680cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:15:56 +0900 Subject: [PATCH 189/350] Locallise ChildItems to DrawableCarouselBeatmapSet for clarity --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 4 ---- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 239fa9ec4e..0ba8cfeb28 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -923,7 +923,7 @@ namespace osu.Game.Tests.Visual.SongSelect if (item is DrawableCarouselBeatmapSet set) { - foreach (var difficulty in set.ChildItems) + foreach (var difficulty in set.DrawableBeatmaps) yield return difficulty; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 21777c5c7c..c011ea7e05 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -640,7 +640,7 @@ namespace osu.Game.Screens.Select if (item is DrawableCarouselBeatmapSet set) { - foreach (var diff in set.ChildItems) + foreach (var diff in set.DrawableBeatmaps) updateItem(diff, item); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e83c460f15..79387a9905 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } - public override IEnumerable ChildItems => beatmapContainer?.Children ?? base.ChildItems; + public IEnumerable DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty(); private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index df8d5cd565..cde3edad39 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -1,9 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -62,8 +60,6 @@ namespace osu.Game.Screens.Select.Carousel } } - public virtual IEnumerable ChildItems => Enumerable.Empty(); - protected DrawableCarouselItem() { RelativeSizeAxes = Axes.X; From 75b6a5e17e23377b33f5ea463ecff07e40ac5b56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:17:23 +0900 Subject: [PATCH 190/350] Remove unnecessary hack (fixed via framework update) --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 79387a9905..cd1c0a08c7 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -113,9 +113,7 @@ namespace osu.Game.Screens.Select.Carousel { var carouselBeatmapSet = (CarouselBeatmapSet)Item; - var visibleBeatmaps = carouselBeatmapSet.Children - .Where(c => c.Visible) - .ToArray(); + var visibleBeatmaps = carouselBeatmapSet.Children.Where(c => c.Visible).ToArray(); // if we are already displaying all the correct beatmaps, only run animation updates. // note that the displayed beatmaps may change due to the applied filter. @@ -129,13 +127,11 @@ namespace osu.Game.Screens.Select.Carousel // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. // can be improved in the future. - beatmapContainer = new Container { X = 100, RelativeSizeAxes = Axes.Both, - // ToArray() in this line is required due to framework oversight: https://github.com/ppy/osu-framework/pull/3929 - ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()).ToArray() + ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()) }; LoadComponentAsync(beatmapContainer, loaded => From 3d416f4d6475c815424e71a8e97a34cb6b933b0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 19:20:46 +0900 Subject: [PATCH 191/350] Clean up beatmapSet resolution in DrawableCarouselBeatmapSet --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index cd1c0a08c7..93dc79242e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -34,10 +35,10 @@ namespace osu.Game.Screens.Select.Carousel public IEnumerable DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty(); - private BeatmapSetInfo beatmapSet => (Item as CarouselBeatmapSet)?.BeatmapSet; - private Container beatmapContainer; + private BeatmapSetInfo beatmapSet; + [Resolved] private BeatmapManager manager { get; set; } @@ -69,6 +70,8 @@ namespace osu.Game.Screens.Select.Carousel if (Item == null) return; + beatmapSet = ((CarouselBeatmapSet)Item).BeatmapSet; + DelayedLoadWrapper background; DelayedLoadWrapper mainFlow; @@ -161,6 +164,8 @@ namespace osu.Game.Screens.Select.Carousel { get { + Debug.Assert(beatmapSet != null); + List items = new List(); if (Item.State.Value == CarouselItemState.NotSelected) @@ -189,6 +194,8 @@ namespace osu.Game.Screens.Select.Carousel private MenuItem createCollectionMenuItem(BeatmapCollection collection) { + Debug.Assert(beatmapSet != null); + TernaryState state; var countExisting = beatmapSet.Beatmaps.Count(b => collection.Beatmaps.Contains(b)); From 8057ea1097b8d3fe570a4183d40e3369ecb82ee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 20:50:36 +0900 Subject: [PATCH 192/350] Fix formatting issues --- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 10 ++++++++-- .../Select/Carousel/FilterableDifficultyIcon.cs | 2 +- .../Select/Carousel/FilterableGroupedDifficultyIcon.cs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index f59cccd8b6..f1120f55a6 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -77,7 +77,10 @@ namespace osu.Game.Screens.Select.Carousel BorderContainer.BorderThickness = 0; BorderContainer.EdgeEffect = new EdgeEffectParameters { - Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, Colour = Color4.Black.Opacity(100), + Type = EdgeEffectType.Shadow, + Offset = new Vector2(1), + Radius = 10, + Colour = Color4.Black.Opacity(100), }; break; @@ -85,7 +88,10 @@ namespace osu.Game.Screens.Select.Carousel BorderContainer.BorderThickness = 2.5f; BorderContainer.EdgeEffect = new EdgeEffectParameters { - Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), Radius = 20, Roundness = 10, + Type = EdgeEffectType.Glow, + Colour = new Color4(130, 204, 255, 150), + Radius = 20, + Roundness = 10, }; break; } diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index 591e9fea22..aa832623fe 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -29,4 +29,4 @@ namespace osu.Game.Screens.Select.Carousel return true; } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs index 73b5781a37..31a651d2c8 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs @@ -35,4 +35,4 @@ namespace osu.Game.Screens.Select.Carousel this.FadeTo(1 - 0.9f * ((float)Items.Count(i => i.Filtered.Value) / Items.Count), 100); } } -} \ No newline at end of file +} From e662dc5342b26ecedd265b55c121a058006e2ad3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Oct 2020 20:57:26 +0900 Subject: [PATCH 193/350] Add missing licence headers --- osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs | 3 +++ .../Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs | 3 +++ osu.Game/Screens/Select/Carousel/SetPanelBackground.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index aa832623fe..dce593b85c 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -1,3 +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 osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs index 31a651d2c8..d2f9ed3a6a 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs @@ -1,3 +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.Collections.Generic; using System.Linq; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs b/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs index 6af0cbf4ab..25139b27db 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelBackground.cs @@ -1,3 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; From 663b8069746a5b79c1acc0b276a31be0e8710825 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 13 Oct 2020 17:45:40 +0200 Subject: [PATCH 194/350] move ModSettingsContainer to seperate component --- .../Overlays/Mods/CModSettingsContainer.cs | 71 +++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 56 +-------------- 2 files changed, 74 insertions(+), 53 deletions(-) create mode 100644 osu.Game/Overlays/Mods/CModSettingsContainer.cs diff --git a/osu.Game/Overlays/Mods/CModSettingsContainer.cs b/osu.Game/Overlays/Mods/CModSettingsContainer.cs new file mode 100644 index 0000000000..a5f33e46c4 --- /dev/null +++ b/osu.Game/Overlays/Mods/CModSettingsContainer.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Mods +{ + public class CModSettingsContainer : Container + { + private readonly FillFlowContainer modSettingsContent; + + public CModSettingsContainer() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0, 0, 0, 192) + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = modSettingsContent = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Padding = new MarginPadding(20), + } + } + }; + } + + ///Bool indicating whether any settings are listed + public bool UpdateModSettings(ValueChangedEvent> mods) + { + modSettingsContent.Clear(); + + foreach (var mod in mods.NewValue) + { + var settings = mod.CreateSettingsControls().ToList(); + if (settings.Count > 0) + modSettingsContent.Add(new ModControlSection(mod, settings)); + } + + bool hasSettings = modSettingsContent.Count > 0; + + if (!hasSettings) + Hide(); + + return hasSettings; + } + + protected override bool OnMouseDown(MouseDownEvent e) => true; + protected override bool OnHover(HoverEvent e) => true; + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f74f40b9b4..b9a37094d7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -45,9 +44,7 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly FillFlowContainer ModSettingsContent; - - protected readonly Container ModSettingsContainer; + protected readonly CModSettingsContainer ModSettingsContainer; public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); @@ -284,7 +281,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new MouseInputAbsorbingContainer + ModSettingsContainer = new CModSettingsContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, @@ -292,27 +289,6 @@ namespace osu.Game.Overlays.Mods Width = 0.25f, Alpha = 0, X = -100, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(0, 0, 0, 192) - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = ModSettingsContent = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Padding = new MarginPadding(20), - } - } - } } }; } @@ -424,7 +400,7 @@ namespace osu.Game.Overlays.Mods updateMods(); - updateModSettings(mods); + CustomiseButton.Enabled.Value = ModSettingsContainer.UpdateModSettings(mods); } private void updateMods() @@ -445,25 +421,6 @@ namespace osu.Game.Overlays.Mods MultiplierLabel.FadeColour(Color4.White, 200); } - private void updateModSettings(ValueChangedEvent> selectedMods) - { - ModSettingsContent.Clear(); - - foreach (var mod in selectedMods.NewValue) - { - var settings = mod.CreateSettingsControls().ToList(); - if (settings.Count > 0) - ModSettingsContent.Add(new ModControlSection(mod, settings)); - } - - bool hasSettings = ModSettingsContent.Count > 0; - - CustomiseButton.Enabled.Value = hasSettings; - - if (!hasSettings) - ModSettingsContainer.Hide(); - } - private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) @@ -495,12 +452,5 @@ namespace osu.Game.Overlays.Mods } #endregion - - protected class MouseInputAbsorbingContainer : Container - { - protected override bool OnMouseDown(MouseDownEvent e) => true; - - protected override bool OnHover(HoverEvent e) => true; - } } } From 28d3295f9f25df1cf60f77dd42353ef736390d6f Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 13 Oct 2020 19:20:15 +0200 Subject: [PATCH 195/350] Test Class Fixes --- .../Visual/UserInterface/TestSceneModSettings.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index a31e244ca5..6a46ff2666 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -30,8 +31,6 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2(); - private readonly Mod testCustomisableMenuCoveredMod = new TestModCustomisable1(); - [SetUp] public void SetUp() => Schedule(() => { @@ -107,8 +106,8 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("change mod settings menu width to full screen", () => modSelect.SetModSettingsWidth(1.0f)); AddStep("select cm2", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.Alpha == 1); - AddStep("hover over mod behind settings menu", () => InputManager.MoveMouseTo(modSelect.GetModButton(testCustomisableMenuCoveredMod))); - AddAssert("Mod is not considered hovered over", () => !modSelect.GetModButton(testCustomisableMenuCoveredMod).IsHovered); + AddStep("hover over mod behind settings menu", () => InputManager.MoveMouseTo(modSelect.GetModButton(testCustomisableMod))); + AddAssert("Mod is not considered hovered over", () => !modSelect.GetModButton(testCustomisableMod).IsHovered); AddStep("left click mod", () => InputManager.Click(MouseButton.Left)); AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); AddStep("right click mod", () => InputManager.Click(MouseButton.Right)); @@ -143,19 +142,14 @@ namespace osu.Game.Tests.Visual.UserInterface public ModButton GetModButton(Mod mod) { - return ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) - .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + return ModSectionsContainer.ChildrenOfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } public void SelectMod(Mod mod) => GetModButton(mod).SelectNext(1); - public float SetModSettingsWidth(float newWidth) - { - float oldWidth = ModSettingsContainer.Width; + public void SetModSettingsWidth(float newWidth) => ModSettingsContainer.Width = newWidth; - return oldWidth; - } } public class TestRulesetInfo : RulesetInfo From 3fd913b13f5a4b317aa5edd45b5cf00de6d45b21 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 13 Oct 2020 19:25:42 +0200 Subject: [PATCH 196/350] rename customisation container class --- ...{CModSettingsContainer.cs => ModCustomisationContainer.cs} | 4 ++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Mods/{CModSettingsContainer.cs => ModCustomisationContainer.cs} (95%) diff --git a/osu.Game/Overlays/Mods/CModSettingsContainer.cs b/osu.Game/Overlays/Mods/ModCustomisationContainer.cs similarity index 95% rename from osu.Game/Overlays/Mods/CModSettingsContainer.cs rename to osu.Game/Overlays/Mods/ModCustomisationContainer.cs index a5f33e46c4..487d92882a 100644 --- a/osu.Game/Overlays/Mods/CModSettingsContainer.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationContainer.cs @@ -16,11 +16,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Mods { - public class CModSettingsContainer : Container + public class ModCustomisationContainer : Container { private readonly FillFlowContainer modSettingsContent; - public CModSettingsContainer() + public ModCustomisationContainer() { Children = new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b9a37094d7..b1ffd26bb9 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly CModSettingsContainer ModSettingsContainer; + protected readonly ModCustomisationContainer ModSettingsContainer; public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); @@ -281,7 +281,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new CModSettingsContainer + ModSettingsContainer = new ModCustomisationContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, From 07e6609e6de38054cbc5e9c64e04f8317da75e8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 14:15:53 +0900 Subject: [PATCH 197/350] Disable difficulty calculation for set-level difficulty icons --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 15 ++++++++++++--- .../Select/Carousel/FilterableDifficultyIcon.cs | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 45327d4514..a1d5e33d1e 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -47,7 +47,10 @@ namespace osu.Game.Beatmaps.Drawables private readonly IReadOnlyList mods; private readonly bool shouldShowTooltip; - private readonly IBindable difficultyBindable = new Bindable(); + + private readonly bool performBackgroundDifficultyLookup; + + private readonly Bindable difficultyBindable = new Bindable(); private Drawable background; @@ -70,10 +73,12 @@ namespace osu.Game.Beatmaps.Drawables /// /// The beatmap to show the difficulty of. /// Whether to display a tooltip when hovered. - public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true) + /// Whether to perform difficulty lookup (including calculation if necessary). + public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) { this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap)); this.shouldShowTooltip = shouldShowTooltip; + this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup; AutoSizeAxes = Axes.Both; @@ -112,9 +117,13 @@ namespace osu.Game.Beatmaps.Drawables // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) Icon = (ruleset ?? beatmap.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } }, - new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0), }; + if (performBackgroundDifficultyLookup) + iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0)); + else + difficultyBindable.Value = new StarDifficulty(beatmap.StarDifficulty, 0); + difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating)); } diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index dce593b85c..51fe7796c7 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselBeatmap Item; public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.Beatmap) + : base(item.Beatmap, performBackgroundDifficultyLookup: false) { filtered.BindTo(item.Filtered); filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); From 30e1fce7a4e82c7b595eb25115c9b0c38f0b7da7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 15:10:50 +0900 Subject: [PATCH 198/350] Reduce alloc overhead of DrawableCarouselBeatmapSet using new function-based ctor --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 93dc79242e..703b91c517 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -77,11 +77,11 @@ namespace osu.Game.Screens.Select.Carousel Header.Children = new Drawable[] { - background = new DelayedLoadWrapper(new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, }, 300), - mainFlow = new DelayedLoadWrapper(new SetPanelContent((CarouselBeatmapSet)Item), 100), + mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 100), }; background.DelayedLoadComplete += fadeContentIn; From 24eff8c66d8aab85a6509f9ea095c13cd5e8a09e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 15:13:49 +0900 Subject: [PATCH 199/350] Rename container to match "settings" term used everywhere --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- .../{ModCustomisationContainer.cs => ModSettingsContainer.cs} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Mods/{ModCustomisationContainer.cs => ModSettingsContainer.cs} (95%) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b1ffd26bb9..2d8b4dba7c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly ModCustomisationContainer ModSettingsContainer; + protected readonly ModSettingsContainer ModSettingsContainer; public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); @@ -281,7 +281,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new ModCustomisationContainer + ModSettingsContainer = new ModSettingsContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, diff --git a/osu.Game/Overlays/Mods/ModCustomisationContainer.cs b/osu.Game/Overlays/Mods/ModSettingsContainer.cs similarity index 95% rename from osu.Game/Overlays/Mods/ModCustomisationContainer.cs rename to osu.Game/Overlays/Mods/ModSettingsContainer.cs index 487d92882a..0521bc35b8 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationContainer.cs +++ b/osu.Game/Overlays/Mods/ModSettingsContainer.cs @@ -16,11 +16,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Mods { - public class ModCustomisationContainer : Container + public class ModSettingsContainer : Container { private readonly FillFlowContainer modSettingsContent; - public ModCustomisationContainer() + public ModSettingsContainer() { Children = new Drawable[] { From 3e326a9234cb99d743f8ae80de42d85b4a43428c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 15:21:28 +0900 Subject: [PATCH 200/350] Use bindable flow for event propagation --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 +++-- .../Overlays/Mods/ModSettingsContainer.cs | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 2d8b4dba7c..31adf47456 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -289,8 +289,11 @@ namespace osu.Game.Overlays.Mods Width = 0.25f, Alpha = 0, X = -100, + SelectedMods = { BindTarget = SelectedMods }, } }; + + ((IBindable)CustomiseButton.Enabled).BindTo(ModSettingsContainer.HasSettingsForSelection); } [BackgroundDependencyLoader(true)] @@ -399,8 +402,6 @@ namespace osu.Game.Overlays.Mods section.SelectTypes(mods.NewValue.Select(m => m.GetType()).ToList()); updateMods(); - - CustomiseButton.Enabled.Value = ModSettingsContainer.UpdateModSettings(mods); } private void updateMods() diff --git a/osu.Game/Overlays/Mods/ModSettingsContainer.cs b/osu.Game/Overlays/Mods/ModSettingsContainer.cs index 0521bc35b8..b185b56ecd 100644 --- a/osu.Game/Overlays/Mods/ModSettingsContainer.cs +++ b/osu.Game/Overlays/Mods/ModSettingsContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; @@ -18,6 +19,12 @@ namespace osu.Game.Overlays.Mods { public class ModSettingsContainer : Container { + public readonly IBindable> SelectedMods = new Bindable>(Array.Empty()); + + public IBindable HasSettingsForSelection => hasSettingsForSelection; + + private readonly Bindable hasSettingsForSelection = new Bindable(); + private readonly FillFlowContainer modSettingsContent; public ModSettingsContainer() @@ -45,8 +52,14 @@ namespace osu.Game.Overlays.Mods }; } - ///Bool indicating whether any settings are listed - public bool UpdateModSettings(ValueChangedEvent> mods) + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedMods.BindValueChanged(modsChanged, true); + } + + private void modsChanged(ValueChangedEvent> mods) { modSettingsContent.Clear(); @@ -62,7 +75,7 @@ namespace osu.Game.Overlays.Mods if (!hasSettings) Hide(); - return hasSettings; + hasSettingsForSelection.Value = hasSettings; } protected override bool OnMouseDown(MouseDownEvent e) => true; From 4eccb03d71de9dd3ddb8e60c5a854bb92df1515e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 14 Oct 2020 17:08:14 +0900 Subject: [PATCH 201/350] Add copyright notice Co-authored-by: Dean Herbert --- osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs index 421cc0ae04..0f4829028f 100644 --- a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs +++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs @@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils /// /// /// Source: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/arraysorthelper.cs + /// Copyright (c) Microsoft Corporation. All rights reserved. /// internal static class LegacySortHelper { From f9bdb664ee7b8fd1c2a041f6d91b92fedca32d3b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 17:09:01 +0900 Subject: [PATCH 202/350] Update diffcalc test --- osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 2c36e81190..a25551f854 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.3683365342338796d, "diffcalc-test")] + [TestCase(2.3449735700206298d, "diffcalc-test")] public void Test(double expected, string name) => base.Test(expected, name); From 3e6ed6c9ffe327c9767b9fb054e396bf1a295b9d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 17:53:28 +0900 Subject: [PATCH 203/350] Add support for dual stages (keycoop) and score multiplier --- .../Beatmaps/ManiaBeatmap.cs | 9 ++++- .../Beatmaps/ManiaBeatmapConverter.cs | 6 +++- .../Difficulty/ManiaDifficultyAttributes.cs | 1 + .../Difficulty/ManiaDifficultyCalculator.cs | 33 +++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index d1d5adea75..93a9ce3dbd 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -21,13 +21,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// public int TotalColumns => Stages.Sum(g => g.Columns); + /// + /// The total number of columns that were present in this before any user adjustments. + /// + public readonly int OriginalTotalColumns; + /// /// Creates a new . /// /// The initial stages. - public ManiaBeatmap(StageDefinition defaultStage) + /// The total number of columns present before any user adjustments. Defaults to the total columns in . + public ManiaBeatmap(StageDefinition defaultStage, int? originalTotalColumns = null) { Stages.Add(defaultStage); + OriginalTotalColumns = originalTotalColumns ?? defaultStage.Columns; } public override IEnumerable GetStatistics() diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index b17ab3f375..757329c525 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public bool Dual; public readonly bool IsForCurrentRuleset; + private int originalTargetColumns; + // Internal for testing purposes internal FastRandom Random { get; private set; } @@ -65,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps else TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); } + + originalTargetColumns = TargetColumns; } public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); @@ -81,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override Beatmap CreateBeatmap() { - beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }); + beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }, originalTargetColumns); if (Dual) beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns }); diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 3ff665d2c8..0b58d1efc6 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty public class ManiaDifficultyAttributes : DifficultyAttributes { public double GreatHitWindow; + public double ScoreMultiplier; } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index a3694f354b..356621acda 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -47,6 +47,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate, + ScoreMultiplier = getScoreMultiplier(beatmap, mods), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), Skills = skills }; @@ -93,12 +94,44 @@ namespace osu.Game.Rulesets.Mania.Difficulty new ManiaModKey3(), new ManiaModKey4(), new ManiaModKey5(), + new MultiMod(new ManiaModKey5(), new ManiaModDualStages()), new ManiaModKey6(), + new MultiMod(new ManiaModKey6(), new ManiaModDualStages()), new ManiaModKey7(), + new MultiMod(new ManiaModKey7(), new ManiaModDualStages()), new ManiaModKey8(), + new MultiMod(new ManiaModKey8(), new ManiaModDualStages()), new ManiaModKey9(), + new MultiMod(new ManiaModKey9(), new ManiaModDualStages()), }).ToArray(); } } + + private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods) + { + double scoreMultiplier = 1; + + foreach (var m in mods) + { + switch (m) + { + case ManiaModNoFail _: + case ManiaModEasy _: + case ManiaModHalfTime _: + scoreMultiplier *= 0.5; + break; + } + } + + var maniaBeatmap = (ManiaBeatmap)beatmap; + int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns; + + if (diff > 0) + scoreMultiplier *= 0.9; + else if (diff < 0) + scoreMultiplier *= 0.9 + 0.04 * diff; + + return scoreMultiplier; + } } } From f04aec538fa6286743147eb2e6f7c83c1b6c4a6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 18:12:19 +0900 Subject: [PATCH 204/350] Fix MultiMod throwing exceptions when creating copies --- .../UserInterface/TestSceneModSettings.cs | 18 ++++++++++++++++++ osu.Game/Rulesets/Mods/MultiMod.cs | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index c5ce3751ef..0d43be3f65 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -95,6 +95,24 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, copy.SpeedChange.Value)); } + [Test] + public void TestMultiModSettingsUnboundWhenCopied() + { + MultiMod original = null; + MultiMod copy = null; + + AddStep("create mods", () => + { + original = new MultiMod(new OsuModDoubleTime()); + copy = (MultiMod)original.CreateCopy(); + }); + + AddStep("change property", () => ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value = 2); + + AddAssert("original has new value", () => Precision.AlmostEquals(2.0, ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value)); + AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value)); + } + private void createModSelect() { AddStep("create mod select", () => diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index f7d574d3c7..2107009dbb 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -6,7 +6,7 @@ using System.Linq; namespace osu.Game.Rulesets.Mods { - public class MultiMod : Mod + public sealed class MultiMod : Mod { public override string Name => string.Empty; public override string Acronym => string.Empty; @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mods Mods = mods; } + public override Mod CreateCopy() => new MultiMod(Mods.Select(m => m.CreateCopy()).ToArray()); + public override Type[] IncompatibleMods => Mods.SelectMany(m => m.IncompatibleMods).ToArray(); } } From da8565c0fa653c7b8dee30ec71d8a51d0cdf97f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 18:28:19 +0900 Subject: [PATCH 205/350] Add 10K mod to incompatibility list --- osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 13fdd74113..8fd5950dfb 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Mods typeof(ManiaModKey7), typeof(ManiaModKey8), typeof(ManiaModKey9), + typeof(ManiaModKey10), }.Except(new[] { GetType() }).ToArray(); } } From ace9fbc8d392c6acae2b7a076b6134d5415d5e68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:15:29 +0900 Subject: [PATCH 206/350] Confine available area for HUD components to excluse the song progress area --- osu.Game/Screens/Play/HUDOverlay.cs | 53 +++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 26aefa138b..f20127bc63 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -80,26 +80,49 @@ namespace osu.Game.Screens.Play visibilityContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = new GridContainer { - HealthDisplay = CreateHealthDisplay(), - topScoreContainer = new Container + RelativeSizeAxes = Axes.Both, + Content = new[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + new Drawable[] { - AccuracyCounter = CreateAccuracyCounter(), - ScoreCounter = CreateScoreCounter(), - ComboCounter = CreateComboCounter(), + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + HealthDisplay = CreateHealthDisplay(), + topScoreContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + AccuracyCounter = CreateAccuracyCounter(), + ScoreCounter = CreateScoreCounter(), + ComboCounter = CreateComboCounter(), + }, + }, + ComboCounter = CreateComboCounter(), + ModDisplay = CreateModsContainer(), + HitErrorDisplay = CreateHitErrorDisplayOverlay(), + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), + } + }, }, + new Drawable[] + { + Progress = CreateProgress(), + } }, - Progress = CreateProgress(), - ModDisplay = CreateModsContainer(), - HitErrorDisplay = CreateHitErrorDisplayOverlay(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), - } + RowDimensions = new[] + { + new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.AutoSize) + } + }, }, new FillFlowContainer { From 0cf3e909042f301788d2abd682cc23dfc9013e6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:15:58 +0900 Subject: [PATCH 207/350] Update SongProgress height based on its dynamic height during resize --- osu.Game/Screens/Play/SongProgress.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index aa745f5ba2..acf4640aa4 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -70,7 +70,6 @@ namespace osu.Game.Screens.Play public SongProgress() { Masking = true; - Height = bottom_bar_height + graph_height + handle_size.Y + info_height; Children = new Drawable[] { @@ -148,6 +147,8 @@ namespace osu.Game.Screens.Play bar.CurrentTime = gameplayTime; graph.Progress = (int)(graph.ColumnCount * progress); + + Height = bottom_bar_height + graph_height + handle_size.Y + info_height - graph.Y; } private void updateBarVisibility() From a7f8e26e3572f97b9bd085c202dc214e88bcac5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:51:53 +0900 Subject: [PATCH 208/350] Adjust bottom-right elements positions based on song progress display --- osu.Game/Screens/Play/HUDOverlay.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f20127bc63..9d7b3f55be 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Play private readonly Container topScoreContainer; + private FillFlowContainer bottomRightElements; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -119,16 +121,16 @@ namespace osu.Game.Screens.Play }, RowDimensions = new[] { - new Dimension(GridSizeMode.Distributed), + new Dimension(), new Dimension(GridSizeMode.AutoSize) } }, }, - new FillFlowContainer + bottomRightElements = new FillFlowContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), + X = -5, AutoSizeAxes = Axes.Both, LayoutDuration = fade_duration / 2, LayoutEasing = fade_easing, @@ -209,6 +211,12 @@ namespace osu.Game.Screens.Play replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } + protected override void Update() + { + base.Update(); + bottomRightElements.Y = -Progress.Height; + } + private void replayLoadedValueChanged(ValueChangedEvent e) { PlayerSettingsOverlay.ReplayLoaded = e.NewValue; From d7a52e97fffd13fd780f6d9a8f283c68f3c637c3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 19:03:11 +0900 Subject: [PATCH 209/350] Fix multimod difficulty combinations not generating correctly --- ...DifficultyAdjustmentModCombinationsTest.cs | 39 +++++++++++++++++++ .../Difficulty/DifficultyCalculator.cs | 31 +++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 760a033aff..de0397dc84 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -94,6 +94,38 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA); } + [Test] + public void TestMultiMod1() + { + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModC())).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(4, combinations.Length); + Assert.IsTrue(combinations[0] is ModNoMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is MultiMod); + Assert.IsTrue(combinations[3] is MultiMod); + + Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[2] is ModC); + Assert.IsTrue(((MultiMod)combinations[3]).Mods[0] is ModB); + Assert.IsTrue(((MultiMod)combinations[3]).Mods[1] is ModC); + } + + [Test] + public void TestMultiMod2() + { + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModIncompatibleWithA())).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(3, combinations.Length); + Assert.IsTrue(combinations[0] is ModNoMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is MultiMod); + + Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModB); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModIncompatibleWithA); + } + private class ModA : Mod { public override string Name => nameof(ModA); @@ -112,6 +144,13 @@ namespace osu.Game.Tests.NonVisual public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) }; } + private class ModC : Mod + { + public override string Name => nameof(ModC); + public override string Acronym => nameof(ModC); + public override double ScoreMultiplier => 1; + } + private class ModIncompatibleWithA : Mod { public override string Name => $"Incompatible With {nameof(ModA)}"; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 1902de5bda..9989c750ee 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Difficulty { return createDifficultyAdjustmentModCombinations(Array.Empty(), DifficultyAdjustmentMods).ToArray(); - IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) + static IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { switch (currentSetCount) { @@ -133,13 +133,36 @@ namespace osu.Game.Rulesets.Difficulty for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) { var adjustmentMod = adjustmentSet[i]; - if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)))) - continue; - foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1)) + if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)) + || adjustmentMod.IncompatibleMods.Any(m => m.IsInstanceOfType(c)))) + { + continue; + } + + // Append the new mod. + int newSetCount = currentSetCount; + var newSet = append(currentSet, adjustmentMod, ref newSetCount); + + foreach (var combo in createDifficultyAdjustmentModCombinations(newSet, adjustmentSet, newSetCount, i + 1)) yield return combo; } } + + // Appends a mod to an existing enumerable, returning the result. Recurses for MultiMod. + static IEnumerable append(IEnumerable existing, Mod mod, ref int count) + { + if (mod is MultiMod multi) + { + foreach (var nested in multi.Mods) + existing = append(existing, nested, ref count); + + return existing; + } + + count++; + return existing.Append(mod); + } } /// From 98acf1e31dc86ea3a0872a4eea5f0043ea2ca4b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 19:16:25 +0900 Subject: [PATCH 210/350] Make field read only --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 9d7b3f55be..14ceadac81 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Play private readonly Container topScoreContainer; - private FillFlowContainer bottomRightElements; + private readonly FillFlowContainer bottomRightElements; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; From 60603d2918b81eeecf6efb721913a825079e7664 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 16:45:40 +0900 Subject: [PATCH 211/350] Add skin components and interfaces --- osu.Game/Screens/Play/HUD/IComboCounter.cs | 19 +++++++++++++++++++ osu.Game/Skinning/HUDSkinComponent.cs | 22 ++++++++++++++++++++++ osu.Game/Skinning/HUDSkinComponents.cs | 10 ++++++++++ 3 files changed, 51 insertions(+) create mode 100644 osu.Game/Screens/Play/HUD/IComboCounter.cs create mode 100644 osu.Game/Skinning/HUDSkinComponent.cs create mode 100644 osu.Game/Skinning/HUDSkinComponents.cs diff --git a/osu.Game/Screens/Play/HUD/IComboCounter.cs b/osu.Game/Screens/Play/HUD/IComboCounter.cs new file mode 100644 index 0000000000..ff235bf04e --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IComboCounter.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An interface providing a set of methods to update a combo counter. + /// + public interface IComboCounter : IDrawable + { + /// + /// The current combo to be displayed. + /// + Bindable Current { get; } + } +} diff --git a/osu.Game/Skinning/HUDSkinComponent.cs b/osu.Game/Skinning/HUDSkinComponent.cs new file mode 100644 index 0000000000..041beb68f2 --- /dev/null +++ b/osu.Game/Skinning/HUDSkinComponent.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; + +namespace osu.Game.Skinning +{ + public class HUDSkinComponent : ISkinComponent + { + public readonly HUDSkinComponents Component; + + public HUDSkinComponent(HUDSkinComponents component) + { + Component = component; + } + + protected virtual string ComponentName => Component.ToString(); + + public string LookupName => + string.Join("/", new[] { "HUD", ComponentName }.Where(s => !string.IsNullOrEmpty(s))); + } +} diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs new file mode 100644 index 0000000000..6f3e2cbaf5 --- /dev/null +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Skinning +{ + public enum HUDSkinComponents + { + ComboCounter + } +} From 375146b4898e61e3378e9834a1a024b5c4804529 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 16:45:48 +0900 Subject: [PATCH 212/350] Make HUDOverlay test scene skinnable --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index c192a7b0e0..e2b831b144 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -9,13 +9,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneHUDOverlay : OsuManualInputManagerTestScene + public class TestSceneHUDOverlay : SkinnableTestScene { private HUDOverlay hudOverlay; @@ -107,13 +109,20 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create overlay", () => { - Child = hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + SetContents(() => + { + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); - // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); - action?.Invoke(hudOverlay); + action?.Invoke(hudOverlay); + + return hudOverlay; + }); }); } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } From f5623ee21e2a229b4de574b961037ba487195bf1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 16:46:13 +0900 Subject: [PATCH 213/350] Setup skinnable combo counter component with default implementation --- .../UserInterface/SimpleComboCounter.cs | 3 +- .../Screens/Play/HUD/SkinnableComboCounter.cs | 58 +++++++++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 10 +--- 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index c9790aed46..59e31eff55 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -5,13 +5,14 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Graphics.UserInterface { /// /// Used as an accuracy counter. Represented visually as a percentage. /// - public class SimpleComboCounter : RollingCounter + public class SimpleComboCounter : RollingCounter, IComboCounter { protected override double RollingDuration => 750; diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs new file mode 100644 index 0000000000..a67953c790 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + public class SkinnableComboCounter : SkinnableDrawable, IComboCounter + { + public SkinnableComboCounter() + : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), createDefault) + { + } + + private IComboCounter skinnedCounter; + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + // todo: unnecessary? + if (skinnedCounter != null) + { + Current.UnbindFrom(skinnedCounter.Current); + } + + base.SkinChanged(skin, allowFallback); + + // temporary layout code, will eventually be replaced by the skin layout system. + if (Drawable is SimpleComboCounter) + { + Drawable.BypassAutoSizeAxes = Axes.X; + Drawable.Anchor = Anchor.TopRight; + Drawable.Origin = Anchor.TopLeft; + Drawable.Margin = new MarginPadding { Top = 5, Left = 20 }; + } + else + { + Drawable.BypassAutoSizeAxes = Axes.X; + Drawable.Anchor = Anchor.BottomLeft; + Drawable.Origin = Anchor.BottomLeft; + Drawable.Margin = new MarginPadding { Top = 5, Left = 20 }; + } + + skinnedCounter = (IComboCounter)Drawable; + + Current.BindTo(skinnedCounter.Current); + } + + private static Drawable createDefault(ISkinComponent skinComponent) => new SimpleComboCounter(); + + public Bindable Current { get; } = new Bindable(); + + public void UpdateCombo(int combo, Color4? hitObjectColour = null) => Current.Value = combo; + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 14ceadac81..ee5b4e3f34 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Play private const Easing fade_easing = Easing.Out; public readonly KeyCounterDisplay KeyCounter; - public readonly RollingCounter ComboCounter; + public readonly SkinnableComboCounter ComboCounter; public readonly ScoreCounter ScoreCounter; public readonly RollingCounter AccuracyCounter; public readonly HealthDisplay HealthDisplay; @@ -275,13 +275,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, }; - protected virtual RollingCounter CreateComboCounter() => new SimpleComboCounter - { - BypassAutoSizeAxes = Axes.X, - Anchor = Anchor.TopRight, - Origin = Anchor.TopLeft, - Margin = new MarginPadding { Top = 5, Left = 20 }, - }; + protected virtual SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); protected virtual HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay { From 899bac6ca535a33b8434a79d941134c874547c68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:02:12 +0900 Subject: [PATCH 214/350] Rename catch combo counter for clarity --- .../Skinning/CatchLegacySkinTransformer.cs | 2 +- .../{LegacyComboCounter.cs => LegacyCatchComboCounter.cs} | 4 ++-- .../{SimpleComboCounter.cs => DefaultComboCounter.cs} | 0 .../HUD/{StandardComboCounter.cs => LegacyComboCounter.cs} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game.Rulesets.Catch/Skinning/{LegacyComboCounter.cs => LegacyCatchComboCounter.cs} (96%) rename osu.Game/Graphics/UserInterface/{SimpleComboCounter.cs => DefaultComboCounter.cs} (100%) rename osu.Game/Screens/Play/HUD/{StandardComboCounter.cs => LegacyComboCounter.cs} (100%) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 47224bd195..916b4c5192 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Skinning // For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. if (this.HasFont(comboFont)) - return new LegacyComboCounter(Source); + return new LegacyCatchComboCounter(Source); break; } diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs similarity index 96% rename from osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs rename to osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs index c8abc9e832..34608b07ff 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Skinning /// /// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter. /// - public class LegacyComboCounter : CompositeDrawable, ICatchComboCounter + public class LegacyCatchComboCounter : CompositeDrawable, ICatchComboCounter { private readonly LegacyRollingCounter counter; private readonly LegacyRollingCounter explosion; - public LegacyComboCounter(ISkin skin) + public LegacyCatchComboCounter(ISkin skin) { var fontName = skin.GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; var fontOverlap = skin.GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/DefaultComboCounter.cs similarity index 100% rename from osu.Game/Graphics/UserInterface/SimpleComboCounter.cs rename to osu.Game/Graphics/UserInterface/DefaultComboCounter.cs diff --git a/osu.Game/Screens/Play/HUD/StandardComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs similarity index 100% rename from osu.Game/Screens/Play/HUD/StandardComboCounter.cs rename to osu.Game/Screens/Play/HUD/LegacyComboCounter.cs From 6a6718ebab2963afefacb3f05628ffdb7d48367c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:20:10 +0900 Subject: [PATCH 215/350] Allow bypassing origin/anchor setting of skinnable components It makes little sense to set these when using RelativeSizeAxes.Both --- .../Screens/Play/HUD/SkinnableComboCounter.cs | 1 + osu.Game/Skinning/SkinnableDrawable.cs | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index a67953c790..36f615e9d0 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -14,6 +14,7 @@ namespace osu.Game.Screens.Play.HUD public SkinnableComboCounter() : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), createDefault) { + CentreComponent = false; } private IComboCounter skinnedCounter; diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index d9a5036649..5a48bc4baf 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -19,6 +19,12 @@ namespace osu.Game.Skinning /// public Drawable Drawable { get; private set; } + /// + /// Whether the drawable component should be centered in available space. + /// Defaults to true. + /// + public bool CentreComponent { get; set; } = true; + public new Axes AutoSizeAxes { get => base.AutoSizeAxes; @@ -84,8 +90,13 @@ namespace osu.Game.Skinning if (Drawable != null) { scaling.Invalidate(); - Drawable.Origin = Anchor.Centre; - Drawable.Anchor = Anchor.Centre; + + if (CentreComponent) + { + Drawable.Origin = Anchor.Centre; + Drawable.Anchor = Anchor.Centre; + } + InternalChild = Drawable; } else From 6eb3176776808d3737923a76171293f642ffa95a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:20:44 +0900 Subject: [PATCH 216/350] Add combo incrementing tests to hud overlay test suite --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index e2b831b144..c02075bea9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -21,6 +23,8 @@ namespace osu.Game.Tests.Visual.Gameplay { private HUDOverlay hudOverlay; + private IEnumerable hudOverlays => CreatedDrawables.OfType(); + // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); @@ -28,6 +32,24 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuConfigManager config { get; set; } + [Test] + public void TestComboCounterIncrementing() + { + createNew(); + + AddRepeatStep("increase combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value++; + }, 10); + + AddStep("reset combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value = 0; + }); + } + [Test] public void TestShownByDefault() { @@ -55,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); @@ -91,14 +113,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set keycounter visible false", () => { config.Set(OsuSetting.KeyOverlay, false); - hudOverlay.KeyCounter.AlwaysVisible.Value = false; + hudOverlays.ForEach(h => h.KeyCounter.AlwaysVisible.Value = false); }); - AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent); - AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); + AddStep("set showhud true", () => hudOverlays.ForEach(h => h.ShowHud.Value = true)); AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); @@ -116,6 +138,8 @@ namespace osu.Game.Tests.Visual.Gameplay // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + hudOverlay.ComboCounter.Current.Value = 1; + action?.Invoke(hudOverlay); return hudOverlay; From 2fce064e32d5a57b6c26a56b79a3d029246ddaae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:21:56 +0900 Subject: [PATCH 217/350] Add basic legacy combo counter and updating positioning logic --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ComboCounter.cs | 4 +-- .../Play/HUD}/DefaultComboCounter.cs | 34 +++++++++++++++---- .../Screens/Play/HUD/LegacyComboCounter.cs | 10 +++++- .../Screens/Play/HUD/SkinnableComboCounter.cs | 31 ++--------------- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 12 +++++++ 7 files changed, 54 insertions(+), 41 deletions(-) rename osu.Game/{Graphics/UserInterface => Screens/Play/HUD}/DefaultComboCounter.cs (54%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 09b4f9b761..43b3dd501d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; Add(score); - ComboCounter comboCounter = new StandardComboCounter + ComboCounter comboCounter = new LegacyComboCounter { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index ea50a4a578..d15a8d25ec 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -9,9 +9,9 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Play.HUD { - public abstract class ComboCounter : Container + public abstract class ComboCounter : Container, IComboCounter { - public BindableInt Current = new BindableInt + public Bindable Current { get; } = new BindableInt { MinValue = 0, }; diff --git a/osu.Game/Graphics/UserInterface/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs similarity index 54% rename from osu.Game/Graphics/UserInterface/DefaultComboCounter.cs rename to osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 59e31eff55..1e23319c28 100644 --- a/osu.Game/Graphics/UserInterface/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -4,26 +4,46 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Play.HUD; +using osu.Game.Graphics.UserInterface; +using osuTK; -namespace osu.Game.Graphics.UserInterface +namespace osu.Game.Screens.Play.HUD { - /// - /// Used as an accuracy counter. Represented visually as a percentage. - /// - public class SimpleComboCounter : RollingCounter, IComboCounter + public class DefaultComboCounter : RollingCounter, IComboCounter { + private readonly Vector2 offset = new Vector2(20, 5); + protected override double RollingDuration => 750; - public SimpleComboCounter() + [Resolved(canBeNull: true)] + private HUDOverlay hud { get; set; } + + public DefaultComboCounter() { Current.Value = DisplayedCount = 0; + + Anchor = Anchor.TopCentre; + Origin = Anchor.TopLeft; + + Position = offset; } [BackgroundDependencyLoader] private void load(OsuColour colours) => Colour = colours.BlueLighter; + protected override void Update() + { + base.Update(); + + if (hud != null) + { + // for now align with the score counter. eventually this will be user customisable. + Position += ToLocalSpace(hud.ScoreCounter.ScreenSpaceDrawQuad.TopRight) + offset; + } + } + protected override string FormatCount(int count) { return $@"{count}x"; diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 7301300b8d..8a94d19609 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Play.HUD /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public class StandardComboCounter : ComboCounter + public class LegacyComboCounter : ComboCounter { protected uint ScheduledPopOutCurrentId; @@ -18,6 +18,14 @@ namespace osu.Game.Screens.Play.HUD public new Vector2 PopOutScale = new Vector2(1.6f); + public LegacyComboCounter() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + + Margin = new MarginPadding { Top = 5, Left = 20 }; + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index 36f615e9d0..9f8ad758e4 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -3,9 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; -using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { @@ -21,39 +19,14 @@ namespace osu.Game.Screens.Play.HUD protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - // todo: unnecessary? - if (skinnedCounter != null) - { - Current.UnbindFrom(skinnedCounter.Current); - } - base.SkinChanged(skin, allowFallback); - // temporary layout code, will eventually be replaced by the skin layout system. - if (Drawable is SimpleComboCounter) - { - Drawable.BypassAutoSizeAxes = Axes.X; - Drawable.Anchor = Anchor.TopRight; - Drawable.Origin = Anchor.TopLeft; - Drawable.Margin = new MarginPadding { Top = 5, Left = 20 }; - } - else - { - Drawable.BypassAutoSizeAxes = Axes.X; - Drawable.Anchor = Anchor.BottomLeft; - Drawable.Origin = Anchor.BottomLeft; - Drawable.Margin = new MarginPadding { Top = 5, Left = 20 }; - } - skinnedCounter = (IComboCounter)Drawable; - - Current.BindTo(skinnedCounter.Current); + skinnedCounter.Current.BindTo(Current); } - private static Drawable createDefault(ISkinComponent skinComponent) => new SimpleComboCounter(); + private static Drawable createDefault(ISkinComponent skinComponent) => new DefaultComboCounter(); public Bindable Current { get; } = new Bindable(); - - public void UpdateCombo(int combo, Color4? hitObjectColour = null) => Current.Value = combo; } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ee5b4e3f34..a3547bbc68 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,6 +22,7 @@ using osuTK.Input; namespace osu.Game.Screens.Play { + [Cached] public class HUDOverlay : Container { private const float fade_duration = 400; @@ -104,7 +105,6 @@ namespace osu.Game.Screens.Play { AccuracyCounter = CreateAccuracyCounter(), ScoreCounter = CreateScoreCounter(), - ComboCounter = CreateComboCounter(), }, }, ComboCounter = CreateComboCounter(), diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e38913b13a..b8b9349cc0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -18,6 +18,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; using osuTK.Graphics; namespace osu.Game.Skinning @@ -327,6 +328,17 @@ namespace osu.Game.Skinning { switch (component) { + case HUDSkinComponent hudComponent: + { + switch (hudComponent.Component) + { + case HUDSkinComponents.ComboCounter: + return new LegacyComboCounter(); + } + + return null; + } + case GameplaySkinComponent resultComponent: switch (resultComponent.Component) { From fbbea48c8c45f5dfc1360c6cf749dc35123751a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:51:03 +0900 Subject: [PATCH 218/350] Add score text skinnability --- osu.Game/Screens/Play/HUD/ComboCounter.cs | 43 ++++++++----------- .../Screens/Play/HUD/LegacyComboCounter.cs | 27 ++++++++++-- osu.Game/Skinning/HUDSkinComponents.cs | 3 +- osu.Game/Skinning/LegacySkin.cs | 9 ++++ 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index d15a8d25ec..5bffa18032 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD public bool IsRolling { get; protected set; } - protected SpriteText PopOutCount; + protected Drawable PopOutCount; protected virtual double PopOutDuration => 150; protected virtual float PopOutScale => 2.0f; @@ -37,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD /// protected Easing RollingEasing => Easing.None; - protected SpriteText DisplayedCountSpriteText; + protected Drawable DisplayedCountSpriteText; private int previousValue; @@ -47,30 +48,34 @@ namespace osu.Game.Screens.Play.HUD protected ComboCounter() { AutoSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] + private void load() + { Children = new Drawable[] { - DisplayedCountSpriteText = new OsuSpriteText + DisplayedCountSpriteText = CreateSpriteText().With(s => { - Alpha = 0, - }, - PopOutCount = new OsuSpriteText + s.Alpha = 0; + }), + PopOutCount = CreateSpriteText().With(s => { - Alpha = 0, - Margin = new MarginPadding(0.05f), - } + s.Alpha = 0; + s.Margin = new MarginPadding(0.05f); + }) }; - TextSize = 80; - Current.ValueChanged += combo => updateCount(combo.NewValue == 0); } + protected virtual Drawable CreateSpriteText() => new OsuSpriteText(); + protected override void LoadComplete() { base.LoadComplete(); - DisplayedCountSpriteText.Text = FormatCount(Current.Value); + ((IHasText)DisplayedCountSpriteText).Text = FormatCount(Current.Value); DisplayedCountSpriteText.Anchor = Anchor; DisplayedCountSpriteText.Origin = Origin; @@ -94,20 +99,6 @@ namespace osu.Game.Screens.Play.HUD } } - private float textSize; - - public float TextSize - { - get => textSize; - set - { - textSize = value; - - DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(size: TextSize); - PopOutCount.Font = PopOutCount.Font.With(size: TextSize); - } - } - /// /// Increments the combo by an amount. /// diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 8a94d19609..54a4338885 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -1,8 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osuTK; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { @@ -26,6 +31,22 @@ namespace osu.Game.Screens.Play.HUD Margin = new MarginPadding { Top = 5, Left = 20 }; } + [Resolved] + private ISkinSource skin { get; set; } + + protected override Drawable CreateSpriteText() + { + return skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText(); + + /* + new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }); + */ + } + protected override void LoadComplete() { base.LoadComplete(); @@ -41,7 +62,7 @@ namespace osu.Game.Screens.Play.HUD protected virtual void TransformPopOut(int newValue) { - PopOutCount.Text = FormatCount(newValue); + ((IHasText)PopOutCount).Text = FormatCount(newValue); PopOutCount.ScaleTo(PopOutScale); PopOutCount.FadeTo(PopOutInitialAlpha); @@ -60,13 +81,13 @@ namespace osu.Game.Screens.Play.HUD protected virtual void TransformNoPopOut(int newValue) { - DisplayedCountSpriteText.Text = FormatCount(newValue); + ((IHasText)DisplayedCountSpriteText).Text = FormatCount(newValue); DisplayedCountSpriteText.ScaleTo(1); } protected virtual void TransformPopOutSmall(int newValue) { - DisplayedCountSpriteText.Text = FormatCount(newValue); + ((IHasText)DisplayedCountSpriteText).Text = FormatCount(newValue); DisplayedCountSpriteText.ScaleTo(PopOutSmallScale); DisplayedCountSpriteText.ScaleTo(1, PopOutDuration, PopOutEasing); } diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 6f3e2cbaf5..7577ba066c 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -5,6 +5,7 @@ namespace osu.Game.Skinning { public enum HUDSkinComponents { - ComboCounter + ComboCounter, + ScoreText } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b8b9349cc0..fddea40b04 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,6 +19,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -334,6 +335,14 @@ namespace osu.Game.Skinning { case HUDSkinComponents.ComboCounter: return new LegacyComboCounter(); + + case HUDSkinComponents.ScoreText: + const string font = "score"; + + if (!this.HasFont(font)) + return null; + + return new LegacySpriteText(this, font); } return null; From 9bb8a43bcee61d11f26598fff2228af1f07a4d17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:15:06 +0900 Subject: [PATCH 219/350] Combine LegacyComboCounter and ComboCounter classes --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ComboCounter.cs | 191 ------------------ .../Screens/Play/HUD/LegacyComboCounter.cs | 176 ++++++++++++++-- osu.Game/Skinning/LegacySkin.cs | 1 - 4 files changed, 155 insertions(+), 215 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/ComboCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 43b3dd501d..29e4b1f0cb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; Add(score); - ComboCounter comboCounter = new LegacyComboCounter + LegacyComboCounter comboCounter = new LegacyComboCounter { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs deleted file mode 100644 index 5bffa18032..0000000000 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Screens.Play.HUD -{ - public abstract class ComboCounter : Container, IComboCounter - { - public Bindable Current { get; } = new BindableInt - { - MinValue = 0, - }; - - public bool IsRolling { get; protected set; } - - protected Drawable PopOutCount; - - protected virtual double PopOutDuration => 150; - protected virtual float PopOutScale => 2.0f; - protected virtual Easing PopOutEasing => Easing.None; - protected virtual float PopOutInitialAlpha => 0.75f; - - protected virtual double FadeOutDuration => 100; - - /// - /// Duration in milliseconds for the counter roll-up animation for each element. - /// - protected virtual double RollingDuration => 20; - - /// - /// Easing for the counter rollover animation. - /// - protected Easing RollingEasing => Easing.None; - - protected Drawable DisplayedCountSpriteText; - - private int previousValue; - - /// - /// Base of all combo counters. - /// - protected ComboCounter() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - DisplayedCountSpriteText = CreateSpriteText().With(s => - { - s.Alpha = 0; - }), - PopOutCount = CreateSpriteText().With(s => - { - s.Alpha = 0; - s.Margin = new MarginPadding(0.05f); - }) - }; - - Current.ValueChanged += combo => updateCount(combo.NewValue == 0); - } - - protected virtual Drawable CreateSpriteText() => new OsuSpriteText(); - - protected override void LoadComplete() - { - base.LoadComplete(); - - ((IHasText)DisplayedCountSpriteText).Text = FormatCount(Current.Value); - DisplayedCountSpriteText.Anchor = Anchor; - DisplayedCountSpriteText.Origin = Origin; - - StopRolling(); - } - - private int displayedCount; - - /// - /// Value shown at the current moment. - /// - public virtual int DisplayedCount - { - get => displayedCount; - protected set - { - if (displayedCount.Equals(value)) - return; - - updateDisplayedCount(displayedCount, value, IsRolling); - } - } - - /// - /// Increments the combo by an amount. - /// - /// - public void Increment(int amount = 1) - { - Current.Value += amount; - } - - /// - /// Stops rollover animation, forcing the displayed count to be the actual count. - /// - public void StopRolling() - { - updateCount(false); - } - - protected virtual string FormatCount(int count) - { - return count.ToString(); - } - - protected virtual void OnCountRolling(int currentValue, int newValue) - { - transformRoll(currentValue, newValue); - } - - protected virtual void OnCountIncrement(int currentValue, int newValue) - { - DisplayedCount = newValue; - } - - protected virtual void OnCountChange(int currentValue, int newValue) - { - DisplayedCount = newValue; - } - - private double getProportionalDuration(int currentValue, int newValue) - { - double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; - return difference * RollingDuration; - } - - private void updateDisplayedCount(int currentValue, int newValue, bool rolling) - { - displayedCount = newValue; - if (rolling) - OnDisplayedCountRolling(currentValue, newValue); - else if (currentValue + 1 == newValue) - OnDisplayedCountIncrement(newValue); - else - OnDisplayedCountChange(newValue); - } - - private void updateCount(bool rolling) - { - int prev = previousValue; - previousValue = Current.Value; - - if (!IsLoaded) - return; - - if (!rolling) - { - FinishTransforms(false, nameof(DisplayedCount)); - IsRolling = false; - DisplayedCount = prev; - - if (prev + 1 == Current.Value) - OnCountIncrement(prev, Current.Value); - else - OnCountChange(prev, Current.Value); - } - else - { - OnCountRolling(displayedCount, Current.Value); - IsRolling = true; - } - } - - private void transformRoll(int currentValue, int newValue) - { - this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), RollingEasing); - } - - protected abstract void OnDisplayedCountRolling(int currentValue, int newValue); - protected abstract void OnDisplayedCountIncrement(int newValue); - protected abstract void OnDisplayedCountChange(int newValue); - } -} diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 54a4338885..b62cd1c309 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -2,62 +2,124 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osuTK; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Screens.Play.HUD { /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public class LegacyComboCounter : ComboCounter + public class LegacyComboCounter : CompositeDrawable, IComboCounter { protected uint ScheduledPopOutCurrentId; protected virtual float PopOutSmallScale => 1.1f; protected virtual bool CanPopOutWhileRolling => false; - public new Vector2 PopOutScale = new Vector2(1.6f); + protected Drawable PopOutCount; + protected Drawable DisplayedCountSpriteText; + private int previousValue; + private int displayedCount; public LegacyComboCounter() { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; - Margin = new MarginPadding { Top = 5, Left = 20 }; + Margin = new MarginPadding { Bottom = 20, Left = 20 }; + + Scale = new Vector2(1.6f); } [Resolved] private ISkinSource skin { get; set; } - protected override Drawable CreateSpriteText() + public Bindable Current { get; } = new BindableInt { - return skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText(); + MinValue = 0, + }; - /* - new OsuSpriteText + public bool IsRolling { get; protected set; } + protected virtual double PopOutDuration => 150; + protected virtual float PopOutScale => 1.6f; + protected virtual Easing PopOutEasing => Easing.None; + protected virtual float PopOutInitialAlpha => 0.75f; + protected virtual double FadeOutDuration => 100; + + /// + /// Duration in milliseconds for the counter roll-up animation for each element. + /// + protected virtual double RollingDuration => 20; + + /// + /// Easing for the counter rollover animation. + /// + protected Easing RollingEasing => Easing.None; + + /// + /// Value shown at the current moment. + /// + public virtual int DisplayedCount + { + get => displayedCount; + protected set + { + if (displayedCount.Equals(value)) + return; + + updateDisplayedCount(displayedCount, value, IsRolling); + } + } + + protected Drawable CreateSpriteText() + { + return skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, - }); - */ + }; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + DisplayedCountSpriteText = CreateSpriteText().With(s => + { + s.Alpha = 0; + }), + PopOutCount = CreateSpriteText().With(s => + { + s.Alpha = 0; + s.Margin = new MarginPadding(0.05f); + }) + }; + + Current.ValueChanged += combo => updateCount(combo.NewValue == 0); } protected override void LoadComplete() { base.LoadComplete(); + ((IHasText)DisplayedCountSpriteText).Text = FormatCount(Current.Value); + + DisplayedCountSpriteText.Anchor = Anchor; + DisplayedCountSpriteText.Origin = Origin; PopOutCount.Origin = Origin; PopOutCount.Anchor = Anchor; - } - protected override string FormatCount(int count) - { - return $@"{count}x"; + StopRolling(); } protected virtual void TransformPopOut(int newValue) @@ -101,7 +163,7 @@ namespace osu.Game.Screens.Play.HUD DisplayedCount++; } - protected override void OnCountRolling(int currentValue, int newValue) + protected void OnCountRolling(int currentValue, int newValue) { ScheduledPopOutCurrentId++; @@ -109,10 +171,10 @@ namespace osu.Game.Screens.Play.HUD if (currentValue == 0 && newValue == 0) DisplayedCountSpriteText.FadeOut(FadeOutDuration); - base.OnCountRolling(currentValue, newValue); + transformRoll(currentValue, newValue); } - protected override void OnCountIncrement(int currentValue, int newValue) + protected void OnCountIncrement(int currentValue, int newValue) { ScheduledPopOutCurrentId++; @@ -130,17 +192,17 @@ namespace osu.Game.Screens.Play.HUD }, PopOutDuration); } - protected override void OnCountChange(int currentValue, int newValue) + protected void OnCountChange(int currentValue, int newValue) { ScheduledPopOutCurrentId++; if (newValue == 0) DisplayedCountSpriteText.FadeOut(); - base.OnCountChange(currentValue, newValue); + DisplayedCount = newValue; } - protected override void OnDisplayedCountRolling(int currentValue, int newValue) + protected void OnDisplayedCountRolling(int currentValue, int newValue) { if (newValue == 0) DisplayedCountSpriteText.FadeOut(FadeOutDuration); @@ -153,18 +215,88 @@ namespace osu.Game.Screens.Play.HUD TransformNoPopOut(newValue); } - protected override void OnDisplayedCountChange(int newValue) + protected void OnDisplayedCountChange(int newValue) { DisplayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); TransformNoPopOut(newValue); } - protected override void OnDisplayedCountIncrement(int newValue) + protected void OnDisplayedCountIncrement(int newValue) { DisplayedCountSpriteText.Show(); TransformPopOutSmall(newValue); } + + /// + /// Increments the combo by an amount. + /// + /// + public void Increment(int amount = 1) + { + Current.Value += amount; + } + + /// + /// Stops rollover animation, forcing the displayed count to be the actual count. + /// + public void StopRolling() + { + updateCount(false); + } + + protected string FormatCount(int count) + { + return $@"{count}x"; + } + + private double getProportionalDuration(int currentValue, int newValue) + { + double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; + return difference * RollingDuration; + } + + private void updateDisplayedCount(int currentValue, int newValue, bool rolling) + { + displayedCount = newValue; + if (rolling) + OnDisplayedCountRolling(currentValue, newValue); + else if (currentValue + 1 == newValue) + OnDisplayedCountIncrement(newValue); + else + OnDisplayedCountChange(newValue); + } + + private void updateCount(bool rolling) + { + int prev = previousValue; + previousValue = Current.Value; + + if (!IsLoaded) + return; + + if (!rolling) + { + FinishTransforms(false, nameof(DisplayedCount)); + IsRolling = false; + DisplayedCount = prev; + + if (prev + 1 == Current.Value) + OnCountIncrement(prev, Current.Value); + else + OnCountChange(prev, Current.Value); + } + else + { + OnCountRolling(displayedCount, Current.Value); + IsRolling = true; + } + } + + private void transformRoll(int currentValue, int newValue) + { + this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), RollingEasing); + } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fddea40b04..a4d47dd2f1 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,7 +19,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; -using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning From 7f5ea57bd483bd528accc4b26f6e0f1f808bf660 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:34:51 +0900 Subject: [PATCH 220/350] Clean-up pass (best effort) on LegacyComboCounter --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- .../Screens/Play/HUD/LegacyComboCounter.cs | 359 ++++++++---------- 2 files changed, 158 insertions(+), 203 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 29e4b1f0cb..ab010bee9f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep(@"Hit! :D", delegate { score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current.Value > 0 ? comboCounter.Current.Value - 1 : 0) / 25.0); - comboCounter.Increment(); + comboCounter.Current.Value++; numerator++; denominator++; accuracyCounter.SetFraction(numerator, denominator); diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index b62cd1c309..c96a20405c 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -18,16 +18,34 @@ namespace osu.Game.Screens.Play.HUD /// public class LegacyComboCounter : CompositeDrawable, IComboCounter { - protected uint ScheduledPopOutCurrentId; + public Bindable Current { get; } = new BindableInt { MinValue = 0, }; - protected virtual float PopOutSmallScale => 1.1f; - protected virtual bool CanPopOutWhileRolling => false; + private uint scheduledPopOutCurrentId; + + private const double pop_out_duration = 150; + + private const Easing pop_out_easing = Easing.None; + + private const double fade_out_duration = 100; + + /// + /// Duration in milliseconds for the counter roll-up animation for each element. + /// + private const double rolling_duration = 20; + + private Drawable popOutCount; + + private Drawable displayedCountSpriteText; - protected Drawable PopOutCount; - protected Drawable DisplayedCountSpriteText; private int previousValue; + private int displayedCount; + private bool isRolling; + + [Resolved] + private ISkinSource skin { get; set; } + public LegacyComboCounter() { AutoSizeAxes = Axes.Both; @@ -40,65 +58,38 @@ namespace osu.Game.Screens.Play.HUD Scale = new Vector2(1.6f); } - [Resolved] - private ISkinSource skin { get; set; } - - public Bindable Current { get; } = new BindableInt - { - MinValue = 0, - }; - - public bool IsRolling { get; protected set; } - protected virtual double PopOutDuration => 150; - protected virtual float PopOutScale => 1.6f; - protected virtual Easing PopOutEasing => Easing.None; - protected virtual float PopOutInitialAlpha => 0.75f; - protected virtual double FadeOutDuration => 100; - - /// - /// Duration in milliseconds for the counter roll-up animation for each element. - /// - protected virtual double RollingDuration => 20; - - /// - /// Easing for the counter rollover animation. - /// - protected Easing RollingEasing => Easing.None; - /// /// Value shown at the current moment. /// public virtual int DisplayedCount { get => displayedCount; - protected set + private set { if (displayedCount.Equals(value)) return; - updateDisplayedCount(displayedCount, value, IsRolling); - } - } + if (isRolling) + onDisplayedCountRolling(displayedCount, value); + else if (displayedCount + 1 == value) + onDisplayedCountIncrement(value); + else + onDisplayedCountChange(value); - protected Drawable CreateSpriteText() - { - return skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 40), - UseFullGlyphHeight = false, - }; + displayedCount = value; + } } [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + InternalChildren = new[] { - DisplayedCountSpriteText = CreateSpriteText().With(s => + displayedCountSpriteText = createSpriteText().With(s => { s.Alpha = 0; }), - PopOutCount = CreateSpriteText().With(s => + popOutCount = createSpriteText().With(s => { s.Alpha = 0; s.Margin = new MarginPadding(0.05f); @@ -112,162 +103,16 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - ((IHasText)DisplayedCountSpriteText).Text = FormatCount(Current.Value); + ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value); - DisplayedCountSpriteText.Anchor = Anchor; - DisplayedCountSpriteText.Origin = Origin; - PopOutCount.Origin = Origin; - PopOutCount.Anchor = Anchor; + displayedCountSpriteText.Anchor = Anchor; + displayedCountSpriteText.Origin = Origin; + popOutCount.Origin = Origin; + popOutCount.Anchor = Anchor; - StopRolling(); - } - - protected virtual void TransformPopOut(int newValue) - { - ((IHasText)PopOutCount).Text = FormatCount(newValue); - - PopOutCount.ScaleTo(PopOutScale); - PopOutCount.FadeTo(PopOutInitialAlpha); - PopOutCount.MoveTo(Vector2.Zero); - - PopOutCount.ScaleTo(1, PopOutDuration, PopOutEasing); - PopOutCount.FadeOut(PopOutDuration, PopOutEasing); - PopOutCount.MoveTo(DisplayedCountSpriteText.Position, PopOutDuration, PopOutEasing); - } - - protected virtual void TransformPopOutRolling(int newValue) - { - TransformPopOut(newValue); - TransformPopOutSmall(newValue); - } - - protected virtual void TransformNoPopOut(int newValue) - { - ((IHasText)DisplayedCountSpriteText).Text = FormatCount(newValue); - DisplayedCountSpriteText.ScaleTo(1); - } - - protected virtual void TransformPopOutSmall(int newValue) - { - ((IHasText)DisplayedCountSpriteText).Text = FormatCount(newValue); - DisplayedCountSpriteText.ScaleTo(PopOutSmallScale); - DisplayedCountSpriteText.ScaleTo(1, PopOutDuration, PopOutEasing); - } - - protected virtual void ScheduledPopOutSmall(uint id) - { - // Too late; scheduled task invalidated - if (id != ScheduledPopOutCurrentId) - return; - - DisplayedCount++; - } - - protected void OnCountRolling(int currentValue, int newValue) - { - ScheduledPopOutCurrentId++; - - // Hides displayed count if was increasing from 0 to 1 but didn't finish - if (currentValue == 0 && newValue == 0) - DisplayedCountSpriteText.FadeOut(FadeOutDuration); - - transformRoll(currentValue, newValue); - } - - protected void OnCountIncrement(int currentValue, int newValue) - { - ScheduledPopOutCurrentId++; - - if (DisplayedCount < currentValue) - DisplayedCount++; - - DisplayedCountSpriteText.Show(); - - TransformPopOut(newValue); - - uint newTaskId = ScheduledPopOutCurrentId; - Scheduler.AddDelayed(delegate - { - ScheduledPopOutSmall(newTaskId); - }, PopOutDuration); - } - - protected void OnCountChange(int currentValue, int newValue) - { - ScheduledPopOutCurrentId++; - - if (newValue == 0) - DisplayedCountSpriteText.FadeOut(); - - DisplayedCount = newValue; - } - - protected void OnDisplayedCountRolling(int currentValue, int newValue) - { - if (newValue == 0) - DisplayedCountSpriteText.FadeOut(FadeOutDuration); - else - DisplayedCountSpriteText.Show(); - - if (CanPopOutWhileRolling) - TransformPopOutRolling(newValue); - else - TransformNoPopOut(newValue); - } - - protected void OnDisplayedCountChange(int newValue) - { - DisplayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); - - TransformNoPopOut(newValue); - } - - protected void OnDisplayedCountIncrement(int newValue) - { - DisplayedCountSpriteText.Show(); - - TransformPopOutSmall(newValue); - } - - /// - /// Increments the combo by an amount. - /// - /// - public void Increment(int amount = 1) - { - Current.Value += amount; - } - - /// - /// Stops rollover animation, forcing the displayed count to be the actual count. - /// - public void StopRolling() - { updateCount(false); } - protected string FormatCount(int count) - { - return $@"{count}x"; - } - - private double getProportionalDuration(int currentValue, int newValue) - { - double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; - return difference * RollingDuration; - } - - private void updateDisplayedCount(int currentValue, int newValue, bool rolling) - { - displayedCount = newValue; - if (rolling) - OnDisplayedCountRolling(currentValue, newValue); - else if (currentValue + 1 == newValue) - OnDisplayedCountIncrement(newValue); - else - OnDisplayedCountChange(newValue); - } - private void updateCount(bool rolling) { int prev = previousValue; @@ -279,24 +124,134 @@ namespace osu.Game.Screens.Play.HUD if (!rolling) { FinishTransforms(false, nameof(DisplayedCount)); - IsRolling = false; + isRolling = false; DisplayedCount = prev; if (prev + 1 == Current.Value) - OnCountIncrement(prev, Current.Value); + onCountIncrement(prev, Current.Value); else - OnCountChange(prev, Current.Value); + onCountChange(prev, Current.Value); } else { - OnCountRolling(displayedCount, Current.Value); - IsRolling = true; + onCountRolling(displayedCount, Current.Value); + isRolling = true; } } - private void transformRoll(int currentValue, int newValue) + private void transformPopOut(int newValue) { - this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), RollingEasing); + ((IHasText)popOutCount).Text = formatCount(newValue); + + popOutCount.ScaleTo(1.6f); + popOutCount.FadeTo(0.75f); + popOutCount.MoveTo(Vector2.Zero); + + popOutCount.ScaleTo(1, pop_out_duration, pop_out_easing); + popOutCount.FadeOut(pop_out_duration, pop_out_easing); + popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration, pop_out_easing); } + + private void transformNoPopOut(int newValue) + { + ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); + + displayedCountSpriteText.ScaleTo(1); + } + + private void transformPopOutSmall(int newValue) + { + ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); + displayedCountSpriteText.ScaleTo(1.1f); + displayedCountSpriteText.ScaleTo(1, pop_out_duration, pop_out_easing); + } + + private void scheduledPopOutSmall(uint id) + { + // Too late; scheduled task invalidated + if (id != scheduledPopOutCurrentId) + return; + + DisplayedCount++; + } + + private void onCountIncrement(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + if (DisplayedCount < currentValue) + DisplayedCount++; + + displayedCountSpriteText.Show(); + + transformPopOut(newValue); + + uint newTaskId = scheduledPopOutCurrentId; + + Scheduler.AddDelayed(delegate + { + scheduledPopOutSmall(newTaskId); + }, pop_out_duration); + } + + private void onCountRolling(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + // Hides displayed count if was increasing from 0 to 1 but didn't finish + if (currentValue == 0 && newValue == 0) + displayedCountSpriteText.FadeOut(fade_out_duration); + + transformRoll(currentValue, newValue); + } + + private void onCountChange(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + if (newValue == 0) + displayedCountSpriteText.FadeOut(); + + DisplayedCount = newValue; + } + + private void onDisplayedCountRolling(int currentValue, int newValue) + { + if (newValue == 0) + displayedCountSpriteText.FadeOut(fade_out_duration); + else + displayedCountSpriteText.Show(); + + transformNoPopOut(newValue); + } + + private void onDisplayedCountChange(int newValue) + { + displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); + transformNoPopOut(newValue); + } + + private void onDisplayedCountIncrement(int newValue) + { + displayedCountSpriteText.Show(); + transformPopOutSmall(newValue); + } + + private void transformRoll(int currentValue, int newValue) => + this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), Easing.None); + + private string formatCount(int count) => $@"{count}x"; + + private double getProportionalDuration(int currentValue, int newValue) + { + double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; + return difference * rolling_duration; + } + + private Drawable createSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }; } } From ac4f56403df80e1cfda6c9fb9d94879d63aa0930 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 19:15:52 +0900 Subject: [PATCH 221/350] Adjust size/position --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index c96a20405c..55ce68fcc8 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -53,9 +53,9 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; - Margin = new MarginPadding { Bottom = 20, Left = 20 }; + Margin = new MarginPadding { Bottom = 10, Left = 10 }; - Scale = new Vector2(1.6f); + Scale = new Vector2(1.2f); } /// From 7d2eeb979532fc643eb6df12fed2691a2c417d66 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 19:18:04 +0900 Subject: [PATCH 222/350] Fix test names --- .../NonVisual/DifficultyAdjustmentModCombinationsTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index de0397dc84..917f245f4f 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.NonVisual } [Test] - public void TestMultiMod1() + public void TestMultiModFlattening() { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModC())).CreateDifficultyAdjustmentModCombinations(); @@ -113,7 +113,7 @@ namespace osu.Game.Tests.NonVisual } [Test] - public void TestMultiMod2() + public void TestIncompatibleThroughMultiMod() { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModIncompatibleWithA())).CreateDifficultyAdjustmentModCombinations(); From e9ebeedbe2edd7b1d4e62f9d01d8940b4cf5255a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 19:31:31 +0900 Subject: [PATCH 223/350] Refactor generation --- .../Difficulty/DifficultyCalculator.cs | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 9989c750ee..70f248e072 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -105,10 +105,11 @@ namespace osu.Game.Rulesets.Difficulty /// public Mod[] CreateDifficultyAdjustmentModCombinations() { - return createDifficultyAdjustmentModCombinations(Array.Empty(), DifficultyAdjustmentMods).ToArray(); + return createDifficultyAdjustmentModCombinations(DifficultyAdjustmentMods, Array.Empty()).ToArray(); - static IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) + static IEnumerable createDifficultyAdjustmentModCombinations(ReadOnlyMemory remainingMods, IEnumerable currentSet, int currentSetCount = 0) { + // Return the current set. switch (currentSetCount) { case 0: @@ -128,11 +129,10 @@ namespace osu.Game.Rulesets.Difficulty break; } - // Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod - // combinations in further recursions, so a moving subset is used to eliminate this effect - for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) + // Apply the rest of the remaining mods recursively. + for (int i = 0; i < remainingMods.Length; i++) { - var adjustmentMod = adjustmentSet[i]; + var adjustmentMod = remainingMods.Span[i]; if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)) || adjustmentMod.IncompatibleMods.Any(m => m.IsInstanceOfType(c)))) @@ -141,27 +141,30 @@ namespace osu.Game.Rulesets.Difficulty } // Append the new mod. - int newSetCount = currentSetCount; - var newSet = append(currentSet, adjustmentMod, ref newSetCount); + var (newSet, newSetCount) = flatten(adjustmentMod); - foreach (var combo in createDifficultyAdjustmentModCombinations(newSet, adjustmentSet, newSetCount, i + 1)) + foreach (var combo in createDifficultyAdjustmentModCombinations(remainingMods.Slice(i + 1), currentSet.Concat(newSet), currentSetCount + newSetCount)) yield return combo; } } - // Appends a mod to an existing enumerable, returning the result. Recurses for MultiMod. - static IEnumerable append(IEnumerable existing, Mod mod, ref int count) + // Flattens a mod hierarchy (through MultiMod) as an IEnumerable + static (IEnumerable set, int count) flatten(Mod mod) { - if (mod is MultiMod multi) - { - foreach (var nested in multi.Mods) - existing = append(existing, nested, ref count); + if (!(mod is MultiMod multi)) + return (mod.Yield(), 1); - return existing; + IEnumerable set = Enumerable.Empty(); + int count = 0; + + foreach (var nested in multi.Mods) + { + var (nestedSet, nestedCount) = flatten(nested); + set = set.Concat(nestedSet); + count += nestedCount; } - count++; - return existing.Append(mod); + return (set, count); } } From e3eaba7b2ca46685780041b788dd2d3a229bbd57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 19:39:48 +0900 Subject: [PATCH 224/350] Move ISampleDisabler implementation to Player and FrameStabilityContainer --- .../Gameplay/TestSceneGameplaySamplePlayback.cs | 2 +- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 12 +++++++++--- osu.Game/Screens/Play/GameplayClock.cs | 10 ++-------- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 - osu.Game/Screens/Play/Player.cs | 13 +++++++++++-- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 5bb3851264..6e505b16c2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("get variables", () => { - gameplayClock = Player.ChildrenOfType().First().GameplayClock; + gameplayClock = Player.ChildrenOfType().First(); slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); samples = slider.ChildrenOfType().ToArray(); }); diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 70b3d0c7d4..e4a3a2fe3d 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -18,8 +18,11 @@ namespace osu.Game.Rulesets.UI /// A container which consumes a parent gameplay clock and standardises frame counts for children. /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks. /// - public class FrameStabilityContainer : Container, IHasReplayHandler + [Cached(typeof(ISamplePlaybackDisabler))] + public class FrameStabilityContainer : Container, IHasReplayHandler, ISamplePlaybackDisabler { + private readonly Bindable samplePlaybackDisabled = new Bindable(); + private readonly double gameplayStartTime; /// @@ -35,7 +38,6 @@ namespace osu.Game.Rulesets.UI public GameplayClock GameplayClock => stabilityGameplayClock; [Cached(typeof(GameplayClock))] - [Cached(typeof(ISamplePlaybackDisabler))] private readonly StabilityGameplayClock stabilityGameplayClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) @@ -102,6 +104,8 @@ namespace osu.Game.Rulesets.UI requireMoreUpdateLoops = true; validState = !GameplayClock.IsPaused.Value; + samplePlaybackDisabled.Value = stabilityGameplayClock.ShouldDisableSamplePlayback; + int loops = 0; while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames) @@ -224,6 +228,8 @@ namespace osu.Game.Rulesets.UI public ReplayInputHandler ReplayInputHandler { get; set; } + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; + private class StabilityGameplayClock : GameplayClock { public GameplayClock ParentGameplayClock; @@ -237,7 +243,7 @@ namespace osu.Game.Rulesets.UI { } - protected override bool ShouldDisableSamplePlayback => + public override bool ShouldDisableSamplePlayback => // handle the case where playback is catching up to real-time. base.ShouldDisableSamplePlayback || ParentSampleDisabler?.SamplePlaybackDisabled.Value == true diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index eeea6777c6..4d0872e5bb 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play /// , as this should only be done once to ensure accuracy. /// /// - public class GameplayClock : IFrameBasedClock, ISamplePlaybackDisabler + public class GameplayClock : IFrameBasedClock { private readonly IFrameBasedClock underlyingClock; @@ -28,8 +28,6 @@ namespace osu.Game.Screens.Play /// public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); - private readonly Bindable samplePlaybackDisabled = new Bindable(); - public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; @@ -66,13 +64,11 @@ namespace osu.Game.Screens.Play /// /// Whether nested samples supporting the interface should be paused. /// - protected virtual bool ShouldDisableSamplePlayback => IsPaused.Value; + public virtual bool ShouldDisableSamplePlayback => IsPaused.Value; public void ProcessFrame() { // intentionally not updating the underlying clock (handled externally). - - samplePlaybackDisabled.Value = ShouldDisableSamplePlayback; } public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; @@ -82,7 +78,5 @@ namespace osu.Game.Screens.Play public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; public IClock Source => underlyingClock; - - IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 9f8e55f577..6679e56871 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -54,7 +54,6 @@ namespace osu.Game.Screens.Play public GameplayClock GameplayClock => localGameplayClock; [Cached(typeof(GameplayClock))] - [Cached(typeof(ISamplePlaybackDisabler))] private readonly LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a2a53b4b75..56b212291a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -35,7 +35,8 @@ using osu.Game.Users; namespace osu.Game.Screens.Play { [Cached] - public class Player : ScreenWithBeatmapBackground + [Cached(typeof(ISamplePlaybackDisabler))] + public class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler { /// /// The delay upon completion of the beatmap before displaying the results screen. @@ -55,6 +56,8 @@ namespace osu.Game.Screens.Play // We are managing our own adjustments (see OnEntering/OnExiting). public override bool AllowRateAdjustments => false; + private readonly Bindable samplePlaybackDisabled = new Bindable(); + /// /// Whether gameplay should pause when the game window focus is lost. /// @@ -229,7 +232,11 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); + DrawableRuleset.IsPaused.BindValueChanged(paused => + { + updateGameplayState(); + samplePlaybackDisabled.Value = paused.NewValue; + }); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -752,5 +759,7 @@ namespace osu.Game.Screens.Play } #endregion + + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; } } From c4fdd35223e85e383b26b82eec0e8c1212eb11f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 19:53:37 +0900 Subject: [PATCH 225/350] Fix same-type incompatibility through multimod --- .../DifficultyAdjustmentModCombinationsTest.cs | 14 ++++++++++++++ .../Difficulty/DifficultyCalculator.cs | 18 ++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 917f245f4f..5c7adb3f49 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -126,6 +126,20 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModIncompatibleWithA); } + [Test] + public void TestIncompatibleWithSameInstanceViaMultiMod() + { + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModA(), new ModB())).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(3, combinations.Length); + Assert.IsTrue(combinations[0] is ModNoMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is MultiMod); + + Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); + } + private class ModA : Mod { public override string Name => nameof(ModA); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 70f248e072..55b3d6607c 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using Sentry; namespace osu.Game.Rulesets.Difficulty { @@ -132,18 +134,18 @@ namespace osu.Game.Rulesets.Difficulty // Apply the rest of the remaining mods recursively. for (int i = 0; i < remainingMods.Length; i++) { - var adjustmentMod = remainingMods.Span[i]; + var (nextSet, nextCount) = flatten(remainingMods.Span[i]); - if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)) - || adjustmentMod.IncompatibleMods.Any(m => m.IsInstanceOfType(c)))) - { + // Check if any mods in the next set are incompatible with any of the current set. + if (currentSet.SelectMany(m => m.IncompatibleMods).Any(c => nextSet.Any(c.IsInstanceOfType))) continue; - } - // Append the new mod. - var (newSet, newSetCount) = flatten(adjustmentMod); + // Check if any mods in the next set are the same type as the current set. Mods of the exact same type are not incompatible with themselves. + if (currentSet.Any(c => nextSet.Any(n => c.GetType() == n.GetType()))) + continue; - foreach (var combo in createDifficultyAdjustmentModCombinations(remainingMods.Slice(i + 1), currentSet.Concat(newSet), currentSetCount + newSetCount)) + // If all's good, attach the next set to the current set and recurse further. + foreach (var combo in createDifficultyAdjustmentModCombinations(remainingMods.Slice(i + 1), currentSet.Concat(nextSet), currentSetCount + nextCount)) yield return combo; } } From ed57b1363fdef33c350d1c65404739aca92750bd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 20:08:46 +0900 Subject: [PATCH 226/350] Remove unused usings --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 55b3d6607c..7616c48150 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; @@ -12,7 +11,6 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using Sentry; namespace osu.Game.Rulesets.Difficulty { From 1a2dc8374052f770fbd936de980901ba69909aeb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 20:40:17 +0900 Subject: [PATCH 227/350] Make field readonly --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 757329c525..7a0e3b2b76 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public bool Dual; public readonly bool IsForCurrentRuleset; - private int originalTargetColumns; + private readonly int originalTargetColumns; // Internal for testing purposes internal FastRandom Random { get; private set; } From 26dffbfd3bed7b5eafc28d8885b276bfb778e9a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 20:40:29 +0900 Subject: [PATCH 228/350] Replicate hit window calculation --- .../Difficulty/ManiaDifficultyCalculator.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 356621acda..ade830764d 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -26,11 +26,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty private const double star_scaling_factor = 0.018; private readonly bool isForCurrentRuleset; + private readonly double originalOverallDifficulty; public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); + originalOverallDifficulty = beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; } protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) @@ -46,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty StarRating = skills[0].DifficultyValue() * star_scaling_factor, Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate, + GreatHitWindow = (int)Math.Ceiling(getHitWindow300(mods) / clockRate), ScoreMultiplier = getScoreMultiplier(beatmap, mods), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), Skills = skills @@ -107,6 +109,35 @@ namespace osu.Game.Rulesets.Mania.Difficulty } } + private int getHitWindow300(Mod[] mods) + { + if (isForCurrentRuleset) + { + double od = Math.Min(10.0, Math.Max(0, 10.0 - originalOverallDifficulty)); + return applyModAdjustments(34 + 3 * od, mods); + } + + if (Math.Round(originalOverallDifficulty) > 4) + return applyModAdjustments(34, mods); + + return applyModAdjustments(47, mods); + + static int applyModAdjustments(double value, Mod[] mods) + { + if (mods.Any(m => m is ManiaModHardRock)) + value /= 1.4; + else if (mods.Any(m => m is ManiaModEasy)) + value *= 1.4; + + if (mods.Any(m => m is ManiaModDoubleTime)) + value *= 1.5; + else if (mods.Any(m => m is ManiaModHalfTime)) + value *= 0.75; + + return (int)value; + } + } + private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods) { double scoreMultiplier = 1; From b63303a2a813aef2b4a574ab5657157f52e1e2ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 21:40:49 +0900 Subject: [PATCH 229/350] Fix tests --- .../Gameplay/TestSceneSkinnableSound.cs | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 8f2011e5dd..18eeb0a0e7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -22,27 +21,24 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinnableSound : OsuTestScene { - [Cached(typeof(ISamplePlaybackDisabler))] - private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); - private TestSkinSourceContainer skinSource; private PausableSkinnableSound skinnableSound; [SetUp] - public void SetUp() => Schedule(() => + public void SetUpSteps() { - gameplayClock.IsPaused.Value = false; - - Children = new Drawable[] + AddStep("setup heirarchy", () => { - skinSource = new TestSkinSourceContainer + Children = new Drawable[] { - Clock = gameplayClock, - RelativeSizeAxes = Axes.Both, - Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide")) - }, - }; - }); + skinSource = new TestSkinSourceContainer + { + RelativeSizeAxes = Axes.Both, + Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide")) + }, + }; + }); + } [Test] public void TestStoppedSoundDoesntResumeAfterPause() @@ -62,8 +58,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for sample to stop playing", () => !sample.Playing); - AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); - AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + + AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); AddAssert("sample not playing", () => !sample.Playing); @@ -82,8 +79,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for sample to start playing", () => sample.Playing); - AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); + AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + + AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddUntilStep("wait for sample to start playing", () => sample.Playing); } [Test] @@ -98,10 +98,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); - AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); - AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); - AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddUntilStep("sample not playing", () => !sample.Playing); + + AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); AddAssert("sample not playing", () => !sample.Playing); AddAssert("sample not playing", () => !sample.Playing); @@ -120,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); - AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); + AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); AddStep("trigger skin change", () => skinSource.TriggerSourceChanged()); @@ -133,20 +134,25 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddAssert("new sample stopped", () => !sample.Playing); - AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); AddAssert("new sample not played", () => !sample.Playing); } [Cached(typeof(ISkinSource))] - private class TestSkinSourceContainer : Container, ISkinSource + [Cached(typeof(ISamplePlaybackDisabler))] + private class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler { [Resolved] private ISkinSource source { get; set; } public event Action SourceChanged; + public Bindable SamplePlaybackDisabled { get; } = new Bindable(); + + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => SamplePlaybackDisabled; + public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component); public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT); public SampleChannel GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); From e0210f5c4ce2c8ab00a74b35f9103da01824f148 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 23:52:58 +0900 Subject: [PATCH 230/350] Ignore failed casts to make tests happy --- osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index 9f8ad758e4..f7b6e419ea 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -21,8 +21,8 @@ namespace osu.Game.Screens.Play.HUD { base.SkinChanged(skin, allowFallback); - skinnedCounter = (IComboCounter)Drawable; - skinnedCounter.Current.BindTo(Current); + skinnedCounter = Drawable as IComboCounter; + skinnedCounter?.Current.BindTo(Current); } private static Drawable createDefault(ISkinComponent skinComponent) => new DefaultComboCounter(); From 2ca6c4e377fd861e989649e3be5295826866022d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Oct 2020 23:24:16 +0200 Subject: [PATCH 231/350] Adjust test step names --- .../Visual/Gameplay/TestSceneSkinnableSound.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 18eeb0a0e7..864e88d023 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void SetUpSteps() { - AddStep("setup heirarchy", () => + AddStep("setup hierarchy", () => { Children = new Drawable[] { @@ -58,9 +58,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for sample to stop playing", () => !sample.Playing); - AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); - AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); AddAssert("sample not playing", () => !sample.Playing); @@ -79,10 +79,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for sample to start playing", () => sample.Playing); - AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); - AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddUntilStep("wait for sample to start playing", () => sample.Playing); } @@ -98,11 +98,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); - AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("sample not playing", () => !sample.Playing); - AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddAssert("sample not playing", () => !sample.Playing); AddAssert("sample not playing", () => !sample.Playing); @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); - AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); AddStep("trigger skin change", () => skinSource.TriggerSourceChanged()); @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddAssert("new sample stopped", () => !sample.Playing); - AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); AddAssert("new sample not played", () => !sample.Playing); From b06f59ffdcf99454bb4447b7e4efb936ebb0f399 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 15:35:33 +0900 Subject: [PATCH 232/350] Split out test for combo counter specifically --- .../Visual/Gameplay/TestSceneComboCounter.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs new file mode 100644 index 0000000000..d0c2fb5064 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneComboCounter : SkinnableTestScene + { + private IEnumerable comboCounters => CreatedDrawables.OfType(); + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create combo counters", () => SetContents(() => + { + var comboCounter = new SkinnableComboCounter(); + comboCounter.Current.Value = 1; + return comboCounter; + })); + } + + [Test] + public void TestComboCounterIncrementing() + { + AddRepeatStep("increase combo", () => + { + foreach (var counter in comboCounters) + counter.Current.Value++; + }, 10); + + AddStep("reset combo", () => + { + foreach (var counter in comboCounters) + counter.Current.Value = 0; + }); + } + } +} From d5f2aab52e4ba4f155118fe3450dc7e57a3979a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 15:37:40 +0900 Subject: [PATCH 233/350] Tidy up SkinnableComboCounter class slightly --- osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index f7b6e419ea..c04c50141a 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -2,15 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { public class SkinnableComboCounter : SkinnableDrawable, IComboCounter { + public Bindable Current { get; } = new Bindable(); + public SkinnableComboCounter() - : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), createDefault) + : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), skinComponent => new DefaultComboCounter()) { CentreComponent = false; } @@ -24,9 +25,5 @@ namespace osu.Game.Screens.Play.HUD skinnedCounter = Drawable as IComboCounter; skinnedCounter?.Current.BindTo(Current); } - - private static Drawable createDefault(ISkinComponent skinComponent) => new DefaultComboCounter(); - - public Bindable Current { get; } = new Bindable(); } } From 219cbec6bdaae9f8b2c987a1be923b55d6d9a596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 16:31:21 +0900 Subject: [PATCH 234/350] Split out DefaultScoreCounter and make ScoreCounter abstract --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- .../Graphics/UserInterface/ScoreCounter.cs | 16 +++++++----- .../Screens/Play/HUD/DefaultScoreCounter.cs | 26 +++++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index ab010bee9f..ba165a70f4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay { int numerator = 0, denominator = 0; - ScoreCounter score = new ScoreCounter(7) + ScoreCounter score = new DefaultScoreCounter() { Origin = Anchor.TopRight, Anchor = Anchor.TopRight, diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 73bbe5f03e..17e5ceedb9 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -1,18 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Graphics.UserInterface { - public class ScoreCounter : RollingCounter + public abstract class ScoreCounter : RollingCounter, IScoreCounter { protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; - public bool UseCommaSeparator; + /// + /// Whether comma separators should be displayed. + /// + public bool UseCommaSeparator { get; } /// /// How many leading zeroes the counter has. @@ -23,14 +26,13 @@ namespace osu.Game.Graphics.UserInterface /// Displays score. /// /// How many leading zeroes the counter will have. - public ScoreCounter(uint leading = 0) + /// Whether comma separators should be displayed. + protected ScoreCounter(uint leading = 0, bool useCommaSeparator = false) { + UseCommaSeparator = useCommaSeparator; LeadingZeroes = leading; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.BlueLighter; - protected override double GetProportionalDuration(double currentValue, double newValue) { return currentValue > newValue ? currentValue - newValue : newValue - currentValue; diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs new file mode 100644 index 0000000000..a461b6a067 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Play.HUD +{ + public class DefaultScoreCounter : ScoreCounter + { + public DefaultScoreCounter() + : base(6) + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + } + } +} From e1da64398e279d0eb6fe53bce2a9e2936403558f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 16:32:20 +0900 Subject: [PATCH 235/350] Add and consume skinnable score counter --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- .../TestSceneSkinnableScoreCounter.cs | 47 +++++++++++++++++++ osu.Game/Screens/Play/HUD/IScoreCounter.cs | 19 ++++++++ .../Screens/Play/HUD/SkinnableScoreCounter.cs | 29 ++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 8 +--- osu.Game/Skinning/HUDSkinComponents.cs | 3 +- osu.Game/Skinning/LegacyScoreCounter.cs | 42 +++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 3 ++ 8 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/IScoreCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs create mode 100644 osu.Game/Skinning/LegacyScoreCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index ba165a70f4..34c657bf7f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay { int numerator = 0, denominator = 0; - ScoreCounter score = new DefaultScoreCounter() + ScoreCounter score = new DefaultScoreCounter { Origin = Anchor.TopRight, Anchor = Anchor.TopRight, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs new file mode 100644 index 0000000000..2d5003d1da --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableScoreCounter : SkinnableTestScene + { + private IEnumerable scoreCounters => CreatedDrawables.OfType(); + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create combo counters", () => SetContents(() => + { + var comboCounter = new SkinnableScoreCounter(); + comboCounter.Current.Value = 1; + return comboCounter; + })); + } + + [Test] + public void TestScoreCounterIncrementing() + { + AddStep(@"Reset all", delegate + { + foreach (var s in scoreCounters) + s.Current.Value = 0; + }); + + AddStep(@"Hit! :D", delegate + { + foreach (var s in scoreCounters) + s.Current.Value += 300; + }); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/IScoreCounter.cs b/osu.Game/Screens/Play/HUD/IScoreCounter.cs new file mode 100644 index 0000000000..2d39a64cfe --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IScoreCounter.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An interface providing a set of methods to update a score counter. + /// + public interface IScoreCounter : IDrawable + { + /// + /// The current score to be displayed. + /// + Bindable Current { get; } + } +} diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs new file mode 100644 index 0000000000..a442ad0d9a --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public class SkinnableScoreCounter : SkinnableDrawable, IScoreCounter + { + public Bindable Current { get; } = new Bindable(); + + public SkinnableScoreCounter() + : base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter()) + { + CentreComponent = false; + } + + private IScoreCounter skinnedCounter; + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + skinnedCounter = Drawable as IScoreCounter; + skinnedCounter?.Current.BindTo(Current); + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a3547bbc68..56b8d60bd4 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableComboCounter ComboCounter; - public readonly ScoreCounter ScoreCounter; + public readonly SkinnableScoreCounter ScoreCounter; public readonly RollingCounter AccuracyCounter; public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; @@ -269,11 +269,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 5, Right = 20 }, }; - protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }; + protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); protected virtual SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 7577ba066c..7863161971 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -6,6 +6,7 @@ namespace osu.Game.Skinning public enum HUDSkinComponents { ComboCounter, - ScoreText + ScoreText, + ScoreCounter } } diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs new file mode 100644 index 0000000000..0e1d4fba7f --- /dev/null +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Skinning +{ + public class LegacyScoreCounter : ScoreCounter + { + private readonly ISkin skin; + + protected override double RollingDuration => 1000; + protected override Easing RollingEasing => Easing.Out; + + public new Bindable Current { get; } = new Bindable(); + + public LegacyScoreCounter(ISkin skin) + : base(6) + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + + this.skin = skin; + + // base class uses int for display, but externally we bind to ScoreProcesssor as a double for now. + Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); + + Margin = new MarginPadding { Bottom = 10, Left = 10 }; + Scale = new Vector2(1.2f); + } + + protected sealed override OsuSpriteText CreateSpriteText() => + new LegacySpriteText(skin, "score" /*, true*/) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a4d47dd2f1..8f4539ca6d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -335,6 +335,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.ComboCounter: return new LegacyComboCounter(); + case HUDSkinComponents.ScoreCounter: + return new LegacyScoreCounter(this); + case HUDSkinComponents.ScoreText: const string font = "score"; From 950c47287ca4ce9c4b71f3dc346ba75918341c6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 16:56:05 +0900 Subject: [PATCH 236/350] Fix positioning of score display in HUD overlay --- .../Screens/Play/HUD/DefaultScoreCounter.cs | 12 ++++++ osu.Game/Screens/Play/HUDOverlay.cs | 41 ++++--------------- osu.Game/Skinning/LegacyScoreCounter.cs | 3 +- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index a461b6a067..af78ce4be2 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -5,11 +5,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Screens.Play.HUD { public class DefaultScoreCounter : ScoreCounter { + private readonly Vector2 offset = new Vector2(20, 5); + public DefaultScoreCounter() : base(6) { @@ -17,10 +20,19 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.TopCentre; } + [Resolved(canBeNull: true)] + private HUDOverlay hud { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { Colour = colours.BlueLighter; + + // todo: check if default once health display is skinnable + hud?.ShowHealthbar.BindValueChanged(healthBar => + { + this.MoveToY(healthBar.NewValue ? 30 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING); + }, true); } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 56b8d60bd4..a507eaaa8d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -25,8 +25,9 @@ namespace osu.Game.Screens.Play [Cached] public class HUDOverlay : Container { - private const float fade_duration = 400; - private const Easing fade_easing = Easing.Out; + public const float FADE_DURATION = 400; + + public const Easing FADE_EASING = Easing.Out; public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableComboCounter ComboCounter; @@ -62,8 +63,6 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - private readonly Container topScoreContainer; - private readonly FillFlowContainer bottomRightElements; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; @@ -96,17 +95,8 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { HealthDisplay = CreateHealthDisplay(), - topScoreContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - AccuracyCounter = CreateAccuracyCounter(), - ScoreCounter = CreateScoreCounter(), - }, - }, + AccuracyCounter = CreateAccuracyCounter(), + ScoreCounter = CreateScoreCounter(), ComboCounter = CreateComboCounter(), ModDisplay = CreateModsContainer(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), @@ -132,8 +122,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, X = -5, AutoSizeAxes = Axes.Both, - LayoutDuration = fade_duration / 2, - LayoutEasing = fade_easing, + LayoutDuration = FADE_DURATION / 2, + LayoutEasing = FADE_EASING, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -186,21 +176,8 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, fade_duration, fade_easing))); - - ShowHealthbar.BindValueChanged(healthBar => - { - if (healthBar.NewValue) - { - HealthDisplay.FadeIn(fade_duration, fade_easing); - topScoreContainer.MoveToY(30, fade_duration, fade_easing); - } - else - { - HealthDisplay.FadeOut(fade_duration, fade_easing); - topScoreContainer.MoveToY(0, fade_duration, fade_easing); - } - }, true); + ShowHealthbar.BindValueChanged(healthBar => HealthDisplay.FadeTo(healthBar.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING), true); + ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING))); configShowHud.BindValueChanged(visible => { diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 0e1d4fba7f..f94bef6652 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -28,8 +28,7 @@ namespace osu.Game.Skinning // base class uses int for display, but externally we bind to ScoreProcesssor as a double for now. Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); - Margin = new MarginPadding { Bottom = 10, Left = 10 }; - Scale = new Vector2(1.2f); + Margin = new MarginPadding(10); } protected sealed override OsuSpriteText CreateSpriteText() => From b210147c2e4424f5c935ae28558c10b80a9aa61c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 16:55:47 +0900 Subject: [PATCH 237/350] Update combo counter to read from default score display's position correctly --- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 4 ++-- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 1e23319c28..5ffaf0d388 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -37,10 +37,10 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); - if (hud != null) + if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score) { // for now align with the score counter. eventually this will be user customisable. - Position += ToLocalSpace(hud.ScoreCounter.ScreenSpaceDrawQuad.TopRight) + offset; + Position += ToLocalSpace(score.ScreenSpaceDrawQuad.TopRight) + offset; } } diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 55ce68fcc8..66f4c5edb8 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; - Margin = new MarginPadding { Bottom = 10, Left = 10 }; + Margin = new MarginPadding(10); Scale = new Vector2(1.2f); } From 74c031cfbb385d4f081f0f110b66a33e6c7376f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:10:35 +0900 Subject: [PATCH 238/350] Fix ModOverlay not including "UNRANKED" text in size --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 99c31241f1..68d019bf71 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -48,22 +48,29 @@ namespace osu.Game.Screens.Play.HUD { AutoSizeAxes = Axes.Both; - Children = new Drawable[] + Child = new FillFlowContainer { - iconsContainer = new ReverseChildIDFillFlowContainer + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + iconsContainer = new ReverseChildIDFillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + unrankedText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"/ UNRANKED /", + Font = OsuFont.Numeric.With(size: 12) + } }, - unrankedText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.TopCentre, - Text = @"/ UNRANKED /", - Font = OsuFont.Numeric.With(size: 12) - } }; Current.ValueChanged += mods => From d8d085ede94aedf9051ab85ab035235bab38d215 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:11:02 +0900 Subject: [PATCH 239/350] Align top-right elements with lowest point in score display --- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 10 ++++---- osu.Game/Screens/Play/HUDOverlay.cs | 24 +++++++++++++++---- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index fc80983834..ffcbb06fb3 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -20,14 +20,13 @@ namespace osu.Game.Screens.Play.HUD public readonly VisualSettings VisualSettings; - //public readonly CollectionSettings CollectionSettings; - - //public readonly DiscussionSettings DiscussionSettings; - public PlayerSettingsOverlay() { AlwaysPresent = true; - RelativeSizeAxes = Axes.Both; + + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + AutoSizeAxes = Axes.Both; Child = new FillFlowContainer { @@ -36,7 +35,6 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 20), - Margin = new MarginPadding { Top = 100, Right = 10 }, Children = new PlayerSettingsGroup[] { //CollectionSettings = new CollectionSettings(), diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a507eaaa8d..639da7a3b6 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -64,6 +64,7 @@ namespace osu.Game.Screens.Play public Action RequestSeek; private readonly FillFlowContainer bottomRightElements; + private readonly FillFlowContainer topRightElements; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; @@ -98,9 +99,7 @@ namespace osu.Game.Screens.Play AccuracyCounter = CreateAccuracyCounter(), ScoreCounter = CreateScoreCounter(), ComboCounter = CreateComboCounter(), - ModDisplay = CreateModsContainer(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), } }, }, @@ -116,11 +115,26 @@ namespace osu.Game.Screens.Play } }, }, + topRightElements = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(10), + Spacing = new Vector2(10), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + ModDisplay = CreateModsContainer(), + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), + } + }, bottomRightElements = new FillFlowContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - X = -5, + Margin = new MarginPadding(10), + Spacing = new Vector2(10), AutoSizeAxes = Axes.Both, LayoutDuration = FADE_DURATION / 2, LayoutEasing = FADE_EASING, @@ -191,6 +205,8 @@ namespace osu.Game.Screens.Play protected override void Update() { base.Update(); + + topRightElements.Y = ToLocalSpace(ScoreCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; bottomRightElements.Y = -Progress.Height; } @@ -266,7 +282,6 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding(10), }; protected virtual SongProgress CreateProgress() => new SongProgress @@ -287,7 +302,6 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 20, Right = 20 }, }; protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows); From 5b5ba7df936f159df034467c0786e6ff4d161838 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:22:34 +0900 Subject: [PATCH 240/350] Remove unused offset --- osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index af78ce4be2..1dcfe2e067 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -5,14 +5,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Screens.Play.HUD { public class DefaultScoreCounter : ScoreCounter { - private readonly Vector2 offset = new Vector2(20, 5); - public DefaultScoreCounter() : base(6) { From 9f51327e4b409382f6750c4c8687a66ee59a6d7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:29:40 +0900 Subject: [PATCH 241/350] Fix completely incorrect default positioning logic --- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 1e23319c28..d6a4d30af6 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -23,11 +23,6 @@ namespace osu.Game.Screens.Play.HUD public DefaultComboCounter() { Current.Value = DisplayedCount = 0; - - Anchor = Anchor.TopCentre; - Origin = Anchor.TopLeft; - - Position = offset; } [BackgroundDependencyLoader] @@ -40,7 +35,7 @@ namespace osu.Game.Screens.Play.HUD if (hud != null) { // for now align with the score counter. eventually this will be user customisable. - Position += ToLocalSpace(hud.ScoreCounter.ScreenSpaceDrawQuad.TopRight) + offset; + Position = Parent.ToLocalSpace(hud.ScoreCounter.ScreenSpaceDrawQuad.TopRight) + offset; } } From 37e9f331ad78fb9ff10701888169e5ea47ddea7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:48:50 +0900 Subject: [PATCH 242/350] Simplify score font lookup --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 8 +------- osu.Game/Skinning/HUDSkinComponents.cs | 1 - osu.Game/Skinning/LegacySkin.cs | 15 +++++++-------- osu.Game/Skinning/LegacySpriteText.cs | 2 +- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 55ce68fcc8..5d96a48117 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -6,8 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Skinning; using osuTK; @@ -248,10 +246,6 @@ namespace osu.Game.Screens.Play.HUD return difference * rolling_duration; } - private Drawable createSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 40), - UseFullGlyphHeight = false, - }; + private Drawable createSpriteText() => new LegacySpriteText(skin); } } diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 7577ba066c..06b22dc693 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -6,6 +6,5 @@ namespace osu.Game.Skinning public enum HUDSkinComponents { ComboCounter, - ScoreText } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a4d47dd2f1..ea5a6e4e20 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -324,24 +324,23 @@ namespace osu.Game.Skinning return null; } + private const string score_font = "score"; + + private bool hasScoreFont => this.HasFont(score_font); + public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) { case HUDSkinComponent hudComponent: { + if (!hasScoreFont) + return null; + switch (hudComponent.Component) { case HUDSkinComponents.ComboCounter: return new LegacyComboCounter(); - - case HUDSkinComponents.ScoreText: - const string font = "score"; - - if (!this.HasFont(font)) - return null; - - return new LegacySpriteText(this, font); } return null; diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 773a9dc5c6..858bbcd6a8 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning { private readonly LegacyGlyphStore glyphStore; - public LegacySpriteText(ISkin skin, string font) + public LegacySpriteText(ISkin skin, string font = "score") { Shadow = false; UseFullGlyphHeight = false; From 254eba90080f04398ade62a52c85b67345068954 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:48:50 +0900 Subject: [PATCH 243/350] Add and consume skinnable accuracy counter --- .../UserInterface/PercentageCounter.cs | 4 -- .../Play/HUD/DefaultAccuracyCounter.cs | 41 ++++++++++++++++ osu.Game/Screens/Play/HUD/IAccuracyCounter.cs | 19 ++++++++ .../Play/HUD/SkinnableAccuracyCounter.cs | 29 ++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 11 +---- osu.Game/Skinning/HUDSkinComponents.cs | 3 +- osu.Game/Skinning/LegacyAccuracyCounter.cs | 47 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 3 ++ 8 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/IAccuracyCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs create mode 100644 osu.Game/Skinning/LegacyAccuracyCounter.cs diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index 1ccf7798e5..2d53ec066b 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Utils; @@ -28,9 +27,6 @@ namespace osu.Game.Graphics.UserInterface Current.Value = DisplayedCount = 1.0f; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.BlueLighter; - protected override string FormatCount(double count) => count.FormatAccuracy(); protected override double GetProportionalDuration(double currentValue, double newValue) diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs new file mode 100644 index 0000000000..b286b380e0 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter + { + private readonly Vector2 offset = new Vector2(-20, 5); + + public DefaultAccuracyCounter() + { + Origin = Anchor.TopRight; + } + + [Resolved(canBeNull: true)] + private HUDOverlay hud { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + } + + protected override void Update() + { + base.Update(); + + if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score) + { + // for now align with the score counter. eventually this will be user customisable. + Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopLeft) + offset; + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/IAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/IAccuracyCounter.cs new file mode 100644 index 0000000000..0199250a08 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IAccuracyCounter.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An interface providing a set of methods to update a accuracy counter. + /// + public interface IAccuracyCounter : IDrawable + { + /// + /// The current accuracy to be displayed. + /// + Bindable Current { get; } + } +} diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs new file mode 100644 index 0000000000..76c9c30813 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public class SkinnableAccuracyCounter : SkinnableDrawable, IAccuracyCounter + { + public Bindable Current { get; } = new Bindable(); + + public SkinnableAccuracyCounter() + : base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter()) + { + CentreComponent = false; + } + + private IAccuracyCounter skinnedCounter; + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + skinnedCounter = Drawable as IAccuracyCounter; + skinnedCounter?.Current.BindTo(Current); + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 639da7a3b6..bb35bd3d69 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -32,7 +31,7 @@ namespace osu.Game.Screens.Play public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableComboCounter ComboCounter; public readonly SkinnableScoreCounter ScoreCounter; - public readonly RollingCounter AccuracyCounter; + public readonly SkinnableAccuracyCounter AccuracyCounter; public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; @@ -254,13 +253,7 @@ namespace osu.Game.Screens.Play return base.OnKeyDown(e); } - protected virtual RollingCounter CreateAccuracyCounter() => new PercentageCounter - { - BypassAutoSizeAxes = Axes.X, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopRight, - Margin = new MarginPadding { Top = 5, Right = 20 }, - }; + protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter(); protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index d810fc31d4..d690a23dee 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -6,6 +6,7 @@ namespace osu.Game.Skinning public enum HUDSkinComponents { ComboCounter, - ScoreCounter + ScoreCounter, + AccuracyCounter } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs new file mode 100644 index 0000000000..0f3ac19ce6 --- /dev/null +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter + { + private readonly ISkin skin; + + public LegacyAccuracyCounter(ISkin skin) + { + Origin = Anchor.TopRight; + Scale = new Vector2(0.75f); + + this.skin = skin; + } + + [Resolved(canBeNull: true)] + private HUDOverlay hud { get; set; } + + protected sealed override OsuSpriteText CreateSpriteText() => + new LegacySpriteText(skin, "score" /*, true*/) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; + + protected override void Update() + { + base.Update(); + + if (hud?.ScoreCounter.Drawable is LegacyScoreCounter score) + { + // for now align with the score counter. eventually this will be user customisable. + Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight); + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index c8460ad797..e1cd095ba8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -344,6 +344,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.ScoreCounter: return new LegacyScoreCounter(this); + + case HUDSkinComponents.AccuracyCounter: + return new LegacyAccuracyCounter(this); } return null; From 4f6dd1586939eef71e4578131e4e5f55155421ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:56:37 +0900 Subject: [PATCH 244/350] Add legacy font lookup support for comma/percent --- osu.Game/Skinning/LegacySpriteText.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 858bbcd6a8..8394657b1c 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -34,7 +34,9 @@ namespace osu.Game.Skinning public ITexturedCharacterGlyph Get(string fontName, char character) { - var texture = skin.GetTexture($"{fontName}-{character}"); + var lookup = getLookupName(character); + + var texture = skin.GetTexture($"{fontName}-{lookup}"); if (texture == null) return null; @@ -42,6 +44,24 @@ namespace osu.Game.Skinning return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, null), texture, 1f / texture.ScaleAdjust); } + private static string getLookupName(char character) + { + switch (character) + { + case ',': + return "comma"; + + case '.': + return "dot"; + + case '%': + return "percent"; + + default: + return character.ToString(); + } + } + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); } } From b31a3fbabbe83ec779f789b9286c0f1bb025c1cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 18:11:30 +0900 Subject: [PATCH 245/350] Add test --- .../TestSceneSkinnableAccuracyCounter.cs | 49 +++++++++++++++++++ .../Play/HUD/DefaultAccuracyCounter.cs | 2 + osu.Game/Skinning/LegacyAccuracyCounter.cs | 4 +- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs new file mode 100644 index 0000000000..709929dcb0 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableAccuracyCounter : SkinnableTestScene + { + private IEnumerable accuracyCounters => CreatedDrawables.OfType(); + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create combo counters", () => SetContents(() => + { + var accuracyCounter = new SkinnableAccuracyCounter(); + + accuracyCounter.Current.Value = 1; + + return accuracyCounter; + })); + } + + [Test] + public void TestChangingAccuracy() + { + AddStep(@"Reset all", delegate + { + foreach (var s in accuracyCounters) + s.Current.Value = 1; + }); + + AddStep(@"Hit! :D", delegate + { + foreach (var s in accuracyCounters) + s.Current.Value -= 0.023f; + }); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index b286b380e0..d5d8ec570a 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -16,6 +16,7 @@ namespace osu.Game.Screens.Play.HUD public DefaultAccuracyCounter() { Origin = Anchor.TopRight; + Anchor = Anchor.TopRight; } [Resolved(canBeNull: true)] @@ -34,6 +35,7 @@ namespace osu.Game.Screens.Play.HUD if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score) { // for now align with the score counter. eventually this will be user customisable. + Anchor = Anchor.TopLeft; Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopLeft) + offset; } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 0f3ac19ce6..815580e85f 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -17,7 +17,9 @@ namespace osu.Game.Skinning public LegacyAccuracyCounter(ISkin skin) { + Anchor = Anchor.TopRight; Origin = Anchor.TopRight; + Scale = new Vector2(0.75f); this.skin = skin; @@ -40,7 +42,7 @@ namespace osu.Game.Skinning if (hud?.ScoreCounter.Drawable is LegacyScoreCounter score) { // for now align with the score counter. eventually this will be user customisable. - Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight); + Y = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; } } } From ca74cf824c6fd01b21b78e862cae58bd6eca534b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 18:24:28 +0900 Subject: [PATCH 246/350] Add padding --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 815580e85f..9354b2b3bc 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -21,6 +21,7 @@ namespace osu.Game.Skinning Origin = Anchor.TopRight; Scale = new Vector2(0.75f); + Margin = new MarginPadding(10); this.skin = skin; } From 6983978c989c5063d8d7fb77cbdc5bed95ede85b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 18:30:44 +0900 Subject: [PATCH 247/350] Correct top-right element offset by finding the lower top anchor element --- osu.Game/Screens/Play/HUDOverlay.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index bb35bd3d69..7553f332cd 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; using osuTK; using osuTK.Input; @@ -65,6 +66,8 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; + private Container mainUIElements; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -89,7 +92,7 @@ namespace osu.Game.Screens.Play { new Drawable[] { - new Container + mainUIElements = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -205,7 +208,17 @@ namespace osu.Game.Screens.Play { base.Update(); - topRightElements.Y = ToLocalSpace(ScoreCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; + float topRightOffset = 0; + + // fetch the bottom-most position of any main ui element that is anchored to the top of the screen. + // consider this kind of temporary. + foreach (var d in mainUIElements) + { + if (d is SkinnableDrawable sd && (sd.Drawable.Anchor & Anchor.y0) > 0) + topRightOffset = Math.Max(sd.Drawable.ScreenSpaceDrawQuad.BottomRight.Y, topRightOffset); + } + + topRightElements.Y = ToLocalSpace(new Vector2(0, topRightOffset)).Y; bottomRightElements.Y = -Progress.Height; } From d76365ed1b26252b3c54aef204bbd3312526ad4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 18:38:41 +0900 Subject: [PATCH 248/350] Make container readonly --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 7553f332cd..fa914c0ebc 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; - private Container mainUIElements; + private readonly Container mainUIElements; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; From 70806deba1195c41e5c59414482b6613d6965b33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 19:11:40 +0900 Subject: [PATCH 249/350] Add support for bottom-anchored hit error display --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 19 ++++++++ osu.Game/Configuration/ScoreMeterType.cs | 10 ++++- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 43 +++++++++++++------ .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 8 +++- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 377f305d63..1021ac3760 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -22,8 +22,10 @@ namespace osu.Game.Tests.Visual.Gameplay { private BarHitErrorMeter barMeter; private BarHitErrorMeter barMeter2; + private BarHitErrorMeter barMeter3; private ColourHitErrorMeter colourMeter; private ColourHitErrorMeter colourMeter2; + private ColourHitErrorMeter colourMeter3; private HitWindows hitWindows; public TestSceneHitErrorMeter() @@ -115,6 +117,13 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.CentreLeft, }); + Add(barMeter3 = new BarHitErrorMeter(hitWindows, true) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.CentreLeft, + Rotation = 270, + }); + Add(colourMeter = new ColourHitErrorMeter(hitWindows) { Anchor = Anchor.CentreRight, @@ -128,6 +137,14 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.CentreLeft, Margin = new MarginPadding { Left = 50 } }); + + Add(colourMeter3 = new ColourHitErrorMeter(hitWindows) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.CentreLeft, + Rotation = 270, + Margin = new MarginPadding { Left = 50 } + }); } private void newJudgement(double offset = 0) @@ -140,8 +157,10 @@ namespace osu.Game.Tests.Visual.Gameplay barMeter.OnNewJudgement(judgement); barMeter2.OnNewJudgement(judgement); + barMeter3.OnNewJudgement(judgement); colourMeter.OnNewJudgement(judgement); colourMeter2.OnNewJudgement(judgement); + colourMeter3.OnNewJudgement(judgement); } } } diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index 156c4b1377..b9499c758e 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -16,7 +16,10 @@ namespace osu.Game.Configuration [Description("Hit Error (right)")] HitErrorRight, - [Description("Hit Error (both)")] + [Description("Hit Error (bottom)")] + HitErrorBottom, + + [Description("Hit Error (left+right)")] HitErrorBoth, [Description("Colour (left)")] @@ -25,7 +28,10 @@ namespace osu.Game.Configuration [Description("Colour (right)")] ColourRight, - [Description("Colour (both)")] + [Description("Colour (left+right)")] ColourBoth, + + [Description("Colour (bottom)")] + ColourBottom, } } diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 4d28f00f39..37d10a5320 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -66,54 +66,69 @@ namespace osu.Game.Screens.Play.HUD switch (type.NewValue) { case ScoreMeterType.HitErrorBoth: - createBar(false); - createBar(true); + createBar(Anchor.CentreLeft); + createBar(Anchor.CentreRight); break; case ScoreMeterType.HitErrorLeft: - createBar(false); + createBar(Anchor.CentreLeft); break; case ScoreMeterType.HitErrorRight: - createBar(true); + createBar(Anchor.CentreRight); + break; + + case ScoreMeterType.HitErrorBottom: + createBar(Anchor.BottomCentre); break; case ScoreMeterType.ColourBoth: - createColour(false); - createColour(true); + createColour(Anchor.CentreLeft); + createColour(Anchor.CentreRight); break; case ScoreMeterType.ColourLeft: - createColour(false); + createColour(Anchor.CentreLeft); break; case ScoreMeterType.ColourRight: - createColour(true); + createColour(Anchor.CentreRight); + break; + + case ScoreMeterType.ColourBottom: + createColour(Anchor.BottomCentre); break; } } - private void createBar(bool rightAligned) + private void createBar(Anchor anchor) { + bool rightAligned = (anchor & Anchor.x2) > 0; + bool bottomAligned = (anchor & Anchor.y2) > 0; + var display = new BarHitErrorMeter(hitWindows, rightAligned) { Margin = new MarginPadding(margin), - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = anchor, + Origin = bottomAligned ? Anchor.CentreLeft : anchor, Alpha = 0, + Rotation = bottomAligned ? 270 : 0 }; completeDisplayLoading(display); } - private void createColour(bool rightAligned) + private void createColour(Anchor anchor) { + bool bottomAligned = (anchor & Anchor.y2) > 0; + var display = new ColourHitErrorMeter(hitWindows) { Margin = new MarginPadding(margin), - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = anchor, + Origin = bottomAligned ? Anchor.CentreLeft : anchor, Alpha = 0, + Rotation = bottomAligned ? 270 : 0 }; completeDisplayLoading(display); diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index f99c84fc01..89f135de7f 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -99,7 +99,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Size = new Vector2(10), Icon = FontAwesome.Solid.ShippingFast, Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Origin = Anchor.Centre, + // undo any layout rotation to display the icon the correct orientation + Rotation = -Rotation, }, new SpriteIcon { @@ -107,7 +109,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Size = new Vector2(10), Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Origin = Anchor.Centre, + // undo any layout rotation to display the icon the correct orientation + Rotation = -Rotation, } } }, From e8235757512030f48f8ce423ba57f46b7c153702 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 14 Oct 2020 13:43:56 +0200 Subject: [PATCH 250/350] Lock screen rotation while in gameplay. --- osu.Android/GameplayScreenRotationLocker.cs | 31 +++++++++++++++++++++ osu.Android/OsuGameActivity.cs | 4 +++ osu.Android/OsuGameAndroid.cs | 6 ++++ osu.Android/osu.Android.csproj | 3 +- 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 osu.Android/GameplayScreenRotationLocker.cs diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs new file mode 100644 index 0000000000..d1f4caba52 --- /dev/null +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Android.Content.PM; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game; + +namespace osu.Android +{ + public class GameplayScreenRotationLocker : Component + { + private Bindable localUserPlaying; + + [BackgroundDependencyLoader] + private void load(OsuGame game) + { + localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); + localUserPlaying.BindValueChanged(_ => updateLock()); + } + + private void updateLock() + { + OsuGameActivity.Activity.RunOnUiThread(() => + { + OsuGameActivity.Activity.RequestedOrientation = localUserPlaying.Value ? ScreenOrientation.Locked : ScreenOrientation.FullUser; + }); + } + } +} diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index db73bb7e7f..c2b28f3de4 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -12,10 +12,14 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { + internal static Activity Activity; + protected override Framework.Game CreateGame() => new OsuGameAndroid(); protected override void OnCreate(Bundle savedInstanceState) { + Activity = this; + // The default current directory on android is '/'. // On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage. // In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory. diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 7542a2b997..887a8395e3 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -55,6 +55,12 @@ namespace osu.Android } } + protected override void LoadComplete() + { + base.LoadComplete(); + LoadComponentAsync(new GameplayScreenRotationLocker(), Add); + } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); } } diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 0598a50530..a2638e95c8 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -21,6 +21,7 @@ r8 + @@ -53,4 +54,4 @@ - + \ No newline at end of file From 703f58bb2f0cae677c237a8c206458ce7df34180 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 11:54:11 +0900 Subject: [PATCH 251/350] Remove last.fm support Has been broken for ages, and their service isn't really something people use these days. --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 1 - osu.Game/Users/User.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index c27b5f4b4a..946831d13b 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -135,7 +135,6 @@ namespace osu.Game.Overlays.Profile.Header anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website); // If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index f8bb8f4c6a..89786e3bd8 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -111,9 +111,6 @@ namespace osu.Game.Users [JsonProperty(@"twitter")] public string Twitter; - [JsonProperty(@"lastfm")] - public string Lastfm; - [JsonProperty(@"skype")] public string Skype; From 39a74536f24d8bafd6bf787311024db647fd2757 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 12:48:31 +0900 Subject: [PATCH 252/350] Update inspections --- .idea/.idea.osu.Desktop/.idea/modules.xml | 2 +- osu.sln.DotSettings | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml index fe63f5faf3..680312ad27 100644 --- a/.idea/.idea.osu.Desktop/.idea/modules.xml +++ b/.idea/.idea.osu.Desktop/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 64f3d41acb..3ef419c572 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -199,7 +199,9 @@ WARNING WARNING WARNING + WARNING HINT + WARNING WARNING DO_NOT_SHOW DO_NOT_SHOW @@ -773,6 +775,7 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True True True True From cc41845f56bc1a65fa10e01a1584334b6fd7c063 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 12:49:31 +0900 Subject: [PATCH 253/350] Add missing string function ordinal specifications --- .../Screens/Drawings/DrawingsScreen.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Rulesets/RulesetStore.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 13 +++++++------ osu.Game/Utils/SentryLogger.cs | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index e10154b722..4c3adeae76 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tournament.Screens.Drawings if (string.IsNullOrEmpty(line)) continue; - if (line.ToUpperInvariant().StartsWith("GROUP")) + if (line.ToUpperInvariant().StartsWith("GROUP", StringComparison.Ordinal)) continue; // ReSharper disable once AccessToModifiedClosure diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index c15240a4f6..7b377e481f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats { var pair = SplitKeyVal(line); - bool isCombo = pair.Key.StartsWith(@"Combo"); + bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal); string[] split = pair.Value.Split(','); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d315b213ab..56cced9c04 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -181,7 +181,7 @@ namespace osu.Game if (args?.Length > 0) { - var paths = args.Where(a => !a.StartsWith(@"-")).ToArray(); + var paths = args.Where(a => !a.StartsWith(@"-", StringComparison.Ordinal)).ToArray(); if (paths.Length > 0) Task.Run(() => Import(paths)); } @@ -289,7 +289,7 @@ namespace osu.Game public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { - if (url.StartsWith("/")) + if (url.StartsWith("/", StringComparison.Ordinal)) url = $"{API.Endpoint}{url}"; externalLinkOpener.OpenUrlExternally(url); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5d93f5186b..c12d418771 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets { // todo: StartsWith can be changed to Equals on 2020-11-08 // This is to give users enough time to have their database use new abbreviated info). - if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo)) == null) + if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) context.RulesetInfo.Add(r.RulesetInfo); } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index a9d88e77ad..3dbec23194 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -115,16 +116,16 @@ namespace osu.Game.Skinning currentConfig.MinimumColumnWidth = minWidth; break; - case string _ when pair.Key.StartsWith("Colour"): + case string _ when pair.Key.StartsWith("Colour", StringComparison.Ordinal): HandleColours(currentConfig, line); break; // Custom sprite paths - case string _ when pair.Key.StartsWith("NoteImage"): - case string _ when pair.Key.StartsWith("KeyImage"): - case string _ when pair.Key.StartsWith("Hit"): - case string _ when pair.Key.StartsWith("Stage"): - case string _ when pair.Key.StartsWith("Lighting"): + case string _ when pair.Key.StartsWith("NoteImage", StringComparison.Ordinal): + case string _ when pair.Key.StartsWith("KeyImage", StringComparison.Ordinal): + case string _ when pair.Key.StartsWith("Hit", StringComparison.Ordinal): + case string _ when pair.Key.StartsWith("Stage", StringComparison.Ordinal): + case string _ when pair.Key.StartsWith("Lighting", StringComparison.Ordinal): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 981251784e..e8e41cdbbe 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -45,7 +45,7 @@ namespace osu.Game.Utils // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. if (lastException != null && - lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace)) + lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace, StringComparison.Ordinal)) return; lastException = exception; From 88f74921fb9de5f01ddfb7be72cb145d9ca14a2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 12:49:39 +0900 Subject: [PATCH 254/350] Update with new r# inspections --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index c02075bea9..603b5d4956 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Gameplay createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); AddUntilStep("wait for load", () => hudOverlay.IsAlive); - AddAssert("initial alpha was less than 1", () => initialAlpha != null && initialAlpha < 1); + AddAssert("initial alpha was less than 1", () => initialAlpha < 1); } [Test] diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 0336c74386..1527d20f54 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -79,9 +79,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementNewCombo() { - if (currentPlacement == null) return; - - if (currentPlacement.HitObject is IHasComboInformation c) + if (currentPlacement?.HitObject is IHasComboInformation c) c.NewCombo = NewCombo.Value == TernaryState.True; } From 88ffcb923408c0e9485f2e28bf38efa98409901a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 12:58:34 +0900 Subject: [PATCH 255/350] Update EndsWith usages --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- osu.Game.Tests/WaveformTestBeatmap.cs | 5 +++-- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- osu.Game/Screens/Select/FilterQueryParser.cs | 8 ++++---- osu.Game/Skinning/LegacySkin.cs | 4 ++-- osu.Game/Updater/SimpleUpdateManager.cs | 7 ++++--- 9 files changed, 20 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 8b22309033..0784109158 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { private static readonly DllResourceStore beatmaps_resource_store = TestResources.GetStore(); - private static IEnumerable allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu")); + private static IEnumerable allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal)); [TestCaseSource(nameof(allBeatmaps))] public void TestEncodeDecodeStability(string name) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 3aff390a47..8669235a7a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -394,7 +394,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz"); AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); - AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); + AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!", StringComparison.Ordinal)); } [Test] diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 7dc5ce1d7f..f9613d9e25 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using osu.Framework.Audio; @@ -59,7 +60,7 @@ namespace osu.Game.Tests get { using (var reader = getZipReader()) - return reader.Filenames.First(f => f.EndsWith(".mp3")); + return reader.Filenames.First(f => f.EndsWith(".mp3", StringComparison.Ordinal)); } } @@ -73,7 +74,7 @@ namespace osu.Game.Tests protected override Beatmap CreateBeatmap() { using (var reader = getZipReader()) - using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")))) + using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu", StringComparison.Ordinal)))) using (var beatmapReader = new LineBufferedReader(beatmapStream)) return Decoder.GetDecoder(beatmapReader).Decode(beatmapReader); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4c75069f08..370e82b468 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -389,7 +389,7 @@ namespace osu.Game.Beatmaps protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { // let's make sure there are actually .osu files to import. - string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); + string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)); if (string.IsNullOrEmpty(mapName)) { @@ -417,7 +417,7 @@ namespace osu.Game.Beatmaps { var beatmapInfos = new List(); - foreach (var file in files.Where(f => f.Filename.EndsWith(".osu"))) + foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) { using (var raw = Files.Store.GetStream(file.FileInfo.StoragePath)) using (var ms = new MemoryStream()) // we need a memory stream so we can seek diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index b76d780860..7bc1c8c7b9 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } - public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb"))?.Filename; + public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; public List Files { get; set; } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3292936f5f..b947056ebd 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -279,7 +279,7 @@ namespace osu.Game.Database // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); - foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith)).OrderBy(f => f.Filename)) + foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename)) { using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath)) s.CopyTo(hashable); @@ -593,7 +593,7 @@ namespace osu.Game.Database var fileInfos = new List(); string prefix = reader.Filenames.GetCommonPrefix(); - if (!(prefix.EndsWith("/") || prefix.EndsWith("\\"))) + if (!(prefix.EndsWith("/", StringComparison.Ordinal) || prefix.EndsWith("\\", StringComparison.Ordinal))) prefix = string.Empty; // import files to manager diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 39fa4f777d..fa2beb2652 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -79,10 +79,10 @@ namespace osu.Game.Screens.Select } private static int getLengthScale(string value) => - value.EndsWith("ms") ? 1 : - value.EndsWith("s") ? 1000 : - value.EndsWith("m") ? 60000 : - value.EndsWith("h") ? 3600000 : 1000; + value.EndsWith("ms", StringComparison.Ordinal) ? 1 : + value.EndsWith("s", StringComparison.Ordinal) ? 1000 : + value.EndsWith("m", StringComparison.Ordinal) ? 60000 : + value.EndsWith("h", StringComparison.Ordinal) ? 3600000 : 1000; private static bool parseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e1cd095ba8..069a887f63 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -422,7 +422,7 @@ namespace osu.Game.Skinning // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). string lastPiece = componentName.Split('/').Last(); - yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; + yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) @@ -433,7 +433,7 @@ namespace osu.Game.Skinning // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. // using .EndsWith() is intentional as it ensures parity in all edge cases // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). - lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix)); + lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index b5fcb56c06..4ebf2a7368 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Threading.Tasks; using Newtonsoft.Json; @@ -73,15 +74,15 @@ namespace osu.Game.Updater switch (RuntimeInfo.OS) { case RuntimeInfo.Platform.Windows: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe", StringComparison.Ordinal)); break; case RuntimeInfo.Platform.MacOsx: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip", StringComparison.Ordinal)); break; case RuntimeInfo.Platform.Linux: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage", StringComparison.Ordinal)); break; case RuntimeInfo.Platform.iOS: From aea31d1582e2c6120088e864479c2d6131d3e978 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 13:07:00 +0900 Subject: [PATCH 256/350] Fix editor not seeking by full beat when track is playing This is expected behaviour as my osu-stable, and I still stand behind the reasoning behind it. Closes #10519. --- osu.Game/Screens/Edit/Editor.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7444369e84..c3560dff38 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -597,10 +597,20 @@ namespace osu.Game.Screens.Edit { double amount = e.ShiftPressed ? 4 : 1; + bool trackPlaying = clock.IsRunning; + + if (trackPlaying) + { + // generally users are not looking to perform tiny seeks when the track is playing, + // so seeks should always be by one full beat, bypassing the beatDivisor. + // this multiplication undoes the division that will be applied in the underlying seek operation. + amount *= beatDivisor.Value; + } + if (direction < 1) - clock.SeekBackward(!clock.IsRunning, amount); + clock.SeekBackward(!trackPlaying, amount); else - clock.SeekForward(!clock.IsRunning, amount); + clock.SeekForward(!trackPlaying, amount); } private void exportBeatmap() From 085d8d0ecbfa233c8ad6864ecf03885a3ba9cc7a Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Fri, 16 Oct 2020 06:16:20 +0200 Subject: [PATCH 257/350] Add support for ScorePrefix and ScoreOverlap values in legacy skins --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 8 +++++++- osu.Game/Skinning/LegacyScoreCounter.cs | 11 +++++++++-- osu.Game/Skinning/LegacySkinConfiguration.cs | 2 ++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 9354b2b3bc..0a64545aee 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -15,6 +15,9 @@ namespace osu.Game.Skinning { private readonly ISkin skin; + private readonly string scorePrefix; + private readonly int scoreOverlap; + public LegacyAccuracyCounter(ISkin skin) { Anchor = Anchor.TopRight; @@ -24,16 +27,19 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); this.skin = skin; + scorePrefix = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; + scoreOverlap = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; } [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, "score" /*, true*/) + new LegacySpriteText(skin, scorePrefix) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Spacing = new Vector2(-scoreOverlap, 0) }; protected override void Update() diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index f94bef6652..e931497564 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Skinning { @@ -12,6 +13,9 @@ namespace osu.Game.Skinning { private readonly ISkin skin; + private readonly string scorePrefix; + private readonly int scoreOverlap; + protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; @@ -24,18 +28,21 @@ namespace osu.Game.Skinning Origin = Anchor.TopRight; this.skin = skin; + scorePrefix = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; + scoreOverlap = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; - // base class uses int for display, but externally we bind to ScoreProcesssor as a double for now. + // base class uses int for display, but externally we bind to ScoreProcessor as a double for now. Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); Margin = new MarginPadding(10); } protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, "score" /*, true*/) + new LegacySpriteText(skin, scorePrefix) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Spacing = new Vector2(-scoreOverlap, 0) }; } } diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 828804b9cb..84a834ec22 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -17,6 +17,8 @@ namespace osu.Game.Skinning Version, ComboPrefix, ComboOverlap, + ScorePrefix, + ScoreOverlap, AnimationFramerate, LayeredHitSounds } From 83482ca15c53d95b47bb0324cad7ec45ad298170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 13:21:47 +0900 Subject: [PATCH 258/350] Fix one more missed occurrence --- osu.Game/Scoring/ScoreManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 5a6da53839..cce6153953 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -57,7 +57,7 @@ namespace osu.Game.Scoring if (archive == null) return null; - using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr")))) + using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase)))) { try { From df1db8611c73c0f2d87154e3895d6f8b0f156705 Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Fri, 16 Oct 2020 08:36:20 +0200 Subject: [PATCH 259/350] move skin-specific config retrieval to GetDrawableComponent --- osu.Game/Skinning/HUDSkinComponents.cs | 4 +++- osu.Game/Skinning/LegacyAccuracyCounter.cs | 13 +------------ osu.Game/Skinning/LegacyScoreCounter.cs | 14 +------------- osu.Game/Skinning/LegacySkin.cs | 10 ++++++++++ 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index d690a23dee..6ec575e106 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -7,6 +7,8 @@ namespace osu.Game.Skinning { ComboCounter, ScoreCounter, - AccuracyCounter + ScoreText, + AccuracyCounter, + AccuracyText } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 0a64545aee..6c194a06d3 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -15,9 +15,6 @@ namespace osu.Game.Skinning { private readonly ISkin skin; - private readonly string scorePrefix; - private readonly int scoreOverlap; - public LegacyAccuracyCounter(ISkin skin) { Anchor = Anchor.TopRight; @@ -27,20 +24,12 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); this.skin = skin; - scorePrefix = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; - scoreOverlap = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; } [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, scorePrefix) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Spacing = new Vector2(-scoreOverlap, 0) - }; + protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyText)) as OsuSpriteText ?? new OsuSpriteText(); protected override void Update() { diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index e931497564..41bf35722b 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Skinning { @@ -13,9 +12,6 @@ namespace osu.Game.Skinning { private readonly ISkin skin; - private readonly string scorePrefix; - private readonly int scoreOverlap; - protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; @@ -28,8 +24,6 @@ namespace osu.Game.Skinning Origin = Anchor.TopRight; this.skin = skin; - scorePrefix = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; - scoreOverlap = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; // base class uses int for display, but externally we bind to ScoreProcessor as a double for now. Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); @@ -37,12 +31,6 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, scorePrefix) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Spacing = new Vector2(-scoreOverlap, 0) - }; + protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as OsuSpriteText ?? new OsuSpriteText(); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e1cd095ba8..f5265f2d6e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,6 +19,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -347,6 +348,15 @@ namespace osu.Game.Skinning case HUDSkinComponents.AccuracyCounter: return new LegacyAccuracyCounter(this); + + case HUDSkinComponents.ScoreText: + case HUDSkinComponents.AccuracyText: + string scorePrefix = GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; + int scoreOverlap = GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; + return new LegacySpriteText(this, scorePrefix) + { + Spacing = new Vector2(-scoreOverlap, 0) + }; } return null; From c0a1f2158cdfbc5539a8dd8e9d462c49e1b17c95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 13:42:50 +0900 Subject: [PATCH 260/350] Add basic component structure for skinnable health displays --- .../TestSceneSkinnableHealthDisplay.cs | 62 +++++++++++++++++++ ...althDisplay.cs => DefaultHealthDisplay.cs} | 10 ++- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 9 ++- osu.Game/Screens/Play/HUD/IHealthDisplay.cs | 26 ++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 11 +--- .../Screens/Play/SkinnableHealthDisplay.cs | 47 ++++++++++++++ osu.Game/Skinning/HUDSkinComponents.cs | 3 +- 7 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs rename osu.Game/Screens/Play/HUD/{StandardHealthDisplay.cs => DefaultHealthDisplay.cs} (92%) create mode 100644 osu.Game/Screens/Play/HUD/IHealthDisplay.cs create mode 100644 osu.Game/Screens/Play/SkinnableHealthDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs new file mode 100644 index 0000000000..181fc8ce98 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Screens.Play; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableHealthDisplay : SkinnableTestScene + { + private IEnumerable healthDisplays => CreatedDrawables.OfType(); + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create health displays", () => + { + SetContents(() => new SkinnableHealthDisplay()); + }); + AddStep(@"Reset all", delegate + { + foreach (var s in healthDisplays) + s.Current.Value = 1; + }); + } + + [Test] + public void TestHealthDisplayIncrementing() + { + AddRepeatStep(@"decrease hp", delegate + { + foreach (var healthDisplay in healthDisplays) + healthDisplay.Current.Value -= 0.08f; + }, 10); + + AddRepeatStep(@"increase hp without flash", delegate + { + foreach (var healthDisplay in healthDisplays) + healthDisplay.Current.Value += 0.1f; + }, 3); + + AddRepeatStep(@"increase hp with flash", delegate + { + foreach (var healthDisplay in healthDisplays) + { + healthDisplay.Current.Value += 0.1f; + healthDisplay.Flash(new JudgementResult(null, new OsuJudgement())); + } + }, 3); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs similarity index 92% rename from osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs rename to osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index fc4a1a5d83..ae78d19c2d 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -16,7 +16,7 @@ using osu.Framework.Utils; namespace osu.Game.Screens.Play.HUD { - public class StandardHealthDisplay : HealthDisplay, IHasAccentColour + public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour { /// /// The base opacity of the glow. @@ -71,8 +71,12 @@ namespace osu.Game.Screens.Play.HUD } } - public StandardHealthDisplay() + public DefaultHealthDisplay() { + Size = new Vector2(1, 5); + RelativeSizeAxes = Axes.X; + Margin = new MarginPadding { Top = 20 }; + Children = new Drawable[] { new Box @@ -103,7 +107,7 @@ namespace osu.Game.Screens.Play.HUD GlowColour = colours.BlueDarker; } - public void Flash(JudgementResult result) + public override void Flash(JudgementResult result) { if (!result.IsHit) return; diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index edc9dedf24..5c43e00192 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -12,14 +13,18 @@ namespace osu.Game.Screens.Play.HUD /// A container for components displaying the current player health. /// Gets bound automatically to the when inserted to hierarchy. /// - public abstract class HealthDisplay : Container + public abstract class HealthDisplay : Container, IHealthDisplay { - public readonly BindableDouble Current = new BindableDouble(1) + public Bindable Current { get; } = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; + public virtual void Flash(JudgementResult result) + { + } + /// /// Bind the tracked fields of to this health display. /// diff --git a/osu.Game/Screens/Play/HUD/IHealthDisplay.cs b/osu.Game/Screens/Play/HUD/IHealthDisplay.cs new file mode 100644 index 0000000000..b1a64bd844 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IHealthDisplay.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An interface providing a set of methods to update a health display. + /// + public interface IHealthDisplay : IDrawable + { + /// + /// The current health to be displayed. + /// + Bindable Current { get; } + + /// + /// Flash the display for a specified result type. + /// + /// The result type. + void Flash(JudgementResult result); + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fa914c0ebc..0d92611e0e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play public readonly SkinnableComboCounter ComboCounter; public readonly SkinnableScoreCounter ScoreCounter; public readonly SkinnableAccuracyCounter AccuracyCounter; - public readonly HealthDisplay HealthDisplay; + public readonly SkinnableHealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; public readonly HitErrorDisplay HitErrorDisplay; @@ -272,12 +272,7 @@ namespace osu.Game.Screens.Play protected virtual SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); - protected virtual HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay - { - Size = new Vector2(1, 5), - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20 } - }; + protected virtual SkinnableHealthDisplay CreateHealthDisplay() => new SkinnableHealthDisplay(); protected virtual FailingLayer CreateFailingLayer() => new FailingLayer { @@ -320,7 +315,7 @@ namespace osu.Game.Screens.Play AccuracyCounter?.Current.BindTo(processor.Accuracy); ComboCounter?.Current.BindTo(processor.Combo); - if (HealthDisplay is StandardHealthDisplay shd) + if (HealthDisplay.Drawable is IHealthDisplay shd) processor.NewJudgement += shd.Flash; } diff --git a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/SkinnableHealthDisplay.cs new file mode 100644 index 0000000000..5b77343278 --- /dev/null +++ b/osu.Game/Screens/Play/SkinnableHealthDisplay.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play +{ + public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay + { + public Bindable Current { get; } = new Bindable(); + + public void Flash(JudgementResult result) => skinnedCounter?.Flash(result); + + private HealthProcessor processor; + + public void BindHealthProcessor(HealthProcessor processor) + { + if (this.processor != null) + throw new InvalidOperationException("Can't bind to a processor more than once"); + + this.processor = processor; + + Current.BindTo(processor.Health); + } + + public SkinnableHealthDisplay() + : base(new HUDSkinComponent(HUDSkinComponents.HealthDisplay), _ => new DefaultHealthDisplay()) + { + CentreComponent = false; + } + + private IHealthDisplay skinnedCounter; + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + skinnedCounter = Drawable as IHealthDisplay; + skinnedCounter?.Current.BindTo(Current); + } + } +} diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index d690a23dee..8772704cef 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -7,6 +7,7 @@ namespace osu.Game.Skinning { ComboCounter, ScoreCounter, - AccuracyCounter + AccuracyCounter, + HealthDisplay } } From e89c5c3b3cadfa20f529b76028cf8d9bba5d3fa0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 14:39:02 +0900 Subject: [PATCH 261/350] Add dynamic compile exceptions to fix skin test scenes --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 2 ++ osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 ++ osu.Game/Skinning/SkinManager.cs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4c75069f08..f3586ec0ec 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.IO; @@ -36,6 +37,7 @@ namespace osu.Game.Beatmaps /// /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// + [ExcludeFromDynamicCompile] public partial class BeatmapManager : DownloadableArchiveModelManager, IDisposable { /// diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 16207c7d2a..cb4884aa51 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -13,6 +13,7 @@ using osu.Framework.Development; using osu.Framework.IO.Network; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -23,6 +24,7 @@ namespace osu.Game.Beatmaps { public partial class BeatmapManager { + [ExcludeFromDynamicCompile] private class BeatmapOnlineLookupQueue : IDisposable { private readonly IAPIProvider api; diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 362c99ea3f..f5c0d97c1f 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -8,6 +8,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Logging; +using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Skinning; @@ -17,6 +18,7 @@ namespace osu.Game.Beatmaps { public partial class BeatmapManager { + [ExcludeFromDynamicCompile] private class BeatmapManagerWorkingBeatmap : WorkingBeatmap { private readonly IResourceStore store; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 7af400e807..37a2309e01 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -18,12 +18,14 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO.Archives; namespace osu.Game.Skinning { + [ExcludeFromDynamicCompile] public class SkinManager : ArchiveModelManager, ISkinSource { private readonly AudioManager audio; From 5be9e30cd0a8005eba7c1592c16016cbcb126e92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 14:39:45 +0900 Subject: [PATCH 262/350] Add legacy implementation --- osu.Game/Skinning/LegacyHealthDisplay.cs | 101 +++++++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 3 + 2 files changed, 104 insertions(+) create mode 100644 osu.Game/Skinning/LegacyHealthDisplay.cs diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs new file mode 100644 index 0000000000..26617ea422 --- /dev/null +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay + { + private readonly Skin skin; + private Sprite fill; + private Marker marker; + public Bindable Current { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; + + public LegacyHealthDisplay(Skin skin) + { + this.skin = skin; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = skin.GetTexture("scorebar-bg") + }, + fill = new Sprite + { + Texture = skin.GetTexture("scorebar-colour"), + Position = new Vector2(7.5f, 7.8f) * 1.6f + }, + marker = new Marker(skin) + { + Current = { BindTarget = Current }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(updateHp, true); + } + + private void updateHp(ValueChangedEvent hp) + { + if (fill.Texture != null) + fill.ResizeWidthTo((float)(fill.Texture.DisplayWidth * hp.NewValue), 500, Easing.OutQuint); + } + + protected override void Update() + { + base.Update(); + + marker.Position = fill.Position + new Vector2(fill.DrawWidth, fill.DrawHeight / 2); + } + + public void Flash(JudgementResult result) + { + marker.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } + + private class Marker : CompositeDrawable + { + public Bindable Current { get; } = new Bindable(); + + public Marker(Skin skin) + { + Origin = Anchor.Centre; + + if (skin.GetTexture("scorebar-ki") != null) + { + // TODO: old style (marker changes as health decreases) + } + else + { + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = skin.GetTexture("scorebar-marker"), + Origin = Anchor.Centre, + } + }; + } + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e1cd095ba8..f02d70fc2a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -347,6 +347,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.AccuracyCounter: return new LegacyAccuracyCounter(this); + + case HUDSkinComponents.HealthDisplay: + return new LegacyHealthDisplay(this); } return null; From a810f56ec87f0aeeb1d454bceb72eb6a30cc083d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 14:49:05 +0900 Subject: [PATCH 263/350] Move "flash on hit only" logic to binding --- osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs | 8 +------- osu.Game/Screens/Play/HUDOverlay.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index ae78d19c2d..b550b469e9 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -107,13 +107,7 @@ namespace osu.Game.Screens.Play.HUD GlowColour = colours.BlueDarker; } - public override void Flash(JudgementResult result) - { - if (!result.IsHit) - return; - - Scheduler.AddOnce(flash); - } + public override void Flash(JudgementResult result) => Scheduler.AddOnce(flash); private void flash() { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 0d92611e0e..ac74dc22d3 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -315,8 +315,14 @@ namespace osu.Game.Screens.Play AccuracyCounter?.Current.BindTo(processor.Accuracy); ComboCounter?.Current.BindTo(processor.Combo); - if (HealthDisplay.Drawable is IHealthDisplay shd) - processor.NewJudgement += shd.Flash; + if (HealthDisplay is IHealthDisplay shd) + { + processor.NewJudgement += judgement => + { + if (judgement.IsHit) + shd.Flash(judgement); + }; + } } protected virtual void BindHealthProcessor(HealthProcessor processor) From f28bcabae72a49841b59f7428455004944a9ace6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 14:54:46 +0900 Subject: [PATCH 264/350] Avoid transforms per hp change --- osu.Game/Skinning/LegacyHealthDisplay.cs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 26617ea422..2fac11d7a4 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Screens.Play.HUD; using osuTK; @@ -17,6 +19,9 @@ namespace osu.Game.Skinning private readonly Skin skin; private Sprite fill; private Marker marker; + + private float maxFillWidth; + public Bindable Current { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; public LegacyHealthDisplay(Skin skin) @@ -45,25 +50,18 @@ namespace osu.Game.Skinning Current = { BindTarget = Current }, } }; - } - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.BindValueChanged(updateHp, true); - } - - private void updateHp(ValueChangedEvent hp) - { - if (fill.Texture != null) - fill.ResizeWidthTo((float)(fill.Texture.DisplayWidth * hp.NewValue), 500, Easing.OutQuint); + maxFillWidth = fill.Width; } protected override void Update() { base.Update(); + fill.Width = Interpolation.ValueAt( + Math.Clamp(Clock.ElapsedFrameTime, 0, 200), + fill.Width, (float)Current.Value * maxFillWidth, 0, 200, Easing.OutQuint); + marker.Position = fill.Position + new Vector2(fill.DrawWidth, fill.DrawHeight / 2); } From 6d3a106a868774862b1fd4187cece7729723c30e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 15:10:39 +0900 Subject: [PATCH 265/350] Simplify texture lookups --- osu.Game/Skinning/LegacyHealthDisplay.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 2fac11d7a4..3691dbc731 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Screens.Play.HUD; @@ -22,6 +23,8 @@ namespace osu.Game.Skinning private float maxFillWidth; + private Texture isNewStyle; + public Bindable Current { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; public LegacyHealthDisplay(Skin skin) @@ -34,15 +37,17 @@ namespace osu.Game.Skinning { AutoSizeAxes = Axes.Both; + isNewStyle = getTexture(skin, "marker"); + InternalChildren = new Drawable[] { new Sprite { - Texture = skin.GetTexture("scorebar-bg") + Texture = getTexture(skin, "bg") }, fill = new Sprite { - Texture = skin.GetTexture("scorebar-colour"), + Texture = getTexture(skin, "colour"), Position = new Vector2(7.5f, 7.8f) * 1.6f }, marker = new Marker(skin) @@ -78,7 +83,7 @@ namespace osu.Game.Skinning { Origin = Anchor.Centre; - if (skin.GetTexture("scorebar-ki") != null) + if (getTexture(skin, "ki") != null) { // TODO: old style (marker changes as health decreases) } @@ -88,12 +93,14 @@ namespace osu.Game.Skinning { new Sprite { - Texture = skin.GetTexture("scorebar-marker"), + Texture = getTexture(skin, "marker"), Origin = Anchor.Centre, } }; } } } + + private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); } } From bdebf2f1a4f8127393115d99c8c172ce27fe85a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 16:17:36 +0900 Subject: [PATCH 266/350] Fix skinnable test scene still not working with dynamic compilation --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index a856789d96..fe4f735325 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -35,12 +35,12 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(AudioManager audio, SkinManager skinManager) + private void load(AudioManager audio, SkinManager skinManager, OsuGameBase game) { var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); - defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); + defaultSkin = new DefaultLegacySkin(new NamespacedResourceStore(game.Resources, "Skins/Legacy"), audio); specialSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true); oldSkin = new TestLegacySkin(new SkinInfo { Name = "old-skin" }, new NamespacedResourceStore(dllStore, "Resources/old_skin"), audio, true); } From f0b15813e206bc8074102905cd52c6a986e414f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 16:18:29 +0900 Subject: [PATCH 267/350] Add support for both legacy styles --- .../Screens/Play/SkinnableHealthDisplay.cs | 6 +- osu.Game/Skinning/LegacyHealthDisplay.cs | 140 ++++++++++++------ 2 files changed, 103 insertions(+), 43 deletions(-) diff --git a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/SkinnableHealthDisplay.cs index 5b77343278..d35d15d665 100644 --- a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/SkinnableHealthDisplay.cs @@ -12,7 +12,11 @@ namespace osu.Game.Screens.Play { public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay { - public Bindable Current { get; } = new Bindable(); + public Bindable Current { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 1 + }; public void Flash(JudgementResult result) => skinnedCounter?.Flash(result); diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 3691dbc731..7d9a1dfc15 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -18,14 +18,18 @@ namespace osu.Game.Skinning public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay { private readonly Skin skin; - private Sprite fill; - private Marker marker; + private Drawable fill; + private LegacyMarker marker; private float maxFillWidth; - private Texture isNewStyle; + private bool isNewStyle; - public Bindable Current { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; + public Bindable Current { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 1 + }; public LegacyHealthDisplay(Skin skin) { @@ -37,25 +41,29 @@ namespace osu.Game.Skinning { AutoSizeAxes = Axes.Both; - isNewStyle = getTexture(skin, "marker"); + isNewStyle = getTexture(skin, "marker") != null; - InternalChildren = new Drawable[] + // background implementation is the same for both versions. + AddInternal(new Sprite { Texture = getTexture(skin, "bg") }); + + if (isNewStyle) { - new Sprite + AddRangeInternal(new[] { - Texture = getTexture(skin, "bg") - }, - fill = new Sprite + fill = new LegacyNewStyleFill(skin), + marker = new LegacyNewStyleMarker(skin), + }); + } + else + { + AddRangeInternal(new[] { - Texture = getTexture(skin, "colour"), - Position = new Vector2(7.5f, 7.8f) * 1.6f - }, - marker = new Marker(skin) - { - Current = { BindTarget = Current }, - } - }; + fill = new LegacyOldStyleFill(skin), + marker = new LegacyOldStyleMarker(skin), + }); + } + marker.Current.BindTo(Current); maxFillWidth = fill.Width; } @@ -70,37 +78,85 @@ namespace osu.Game.Skinning marker.Position = fill.Position + new Vector2(fill.DrawWidth, fill.DrawHeight / 2); } - public void Flash(JudgementResult result) - { - marker.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); - } + public void Flash(JudgementResult result) => marker.Flash(result); - private class Marker : CompositeDrawable - { - public Bindable Current { get; } = new Bindable(); + private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); - public Marker(Skin skin) + public class LegacyOldStyleMarker : LegacyMarker + { + public LegacyOldStyleMarker(Skin skin) { - Origin = Anchor.Centre; - - if (getTexture(skin, "ki") != null) + InternalChildren = new Drawable[] { - // TODO: old style (marker changes as health decreases) - } - else - { - InternalChildren = new Drawable[] + new Sprite { - new Sprite - { - Texture = getTexture(skin, "marker"), - Origin = Anchor.Centre, - } - }; - } + Texture = getTexture(skin, "ki"), + Origin = Anchor.Centre, + } + }; } } - private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); + public class LegacyNewStyleMarker : LegacyMarker + { + public LegacyNewStyleMarker(Skin skin) + { + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = getTexture(skin, "marker"), + Origin = Anchor.Centre, + } + }; + } + } + + public class LegacyMarker : CompositeDrawable, IHealthDisplay + { + public Bindable Current { get; } = new Bindable(); + + public LegacyMarker() + { + Origin = Anchor.Centre; + } + + public void Flash(JudgementResult result) + { + this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } + } + + internal class LegacyOldStyleFill : CompositeDrawable + { + public LegacyOldStyleFill(Skin skin) + { + // required for sizing correctly.. + var firstFrame = getTexture(skin, "colour-0"); + + if (firstFrame == null) + { + InternalChild = new Sprite { Texture = getTexture(skin, "colour") }; + Size = InternalChild.Size; + } + else + { + InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Drawable.Empty(); + Size = new Vector2(firstFrame.DisplayWidth, firstFrame.DisplayHeight); + } + + Position = new Vector2(3, 10) * 1.6f; + Masking = true; + } + } + + internal class LegacyNewStyleFill : Sprite + { + public LegacyNewStyleFill(Skin skin) + { + Texture = getTexture(skin, "colour"); + Position = new Vector2(7.5f, 7.8f) * 1.6f; + } + } } } From 9837286aea6ed1ce625197737d587749e29977f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 16:18:57 +0900 Subject: [PATCH 268/350] Add test resources --- osu.Game.Tests/Resources/old-skin/score-0.png | Bin 0 -> 3092 bytes osu.Game.Tests/Resources/old-skin/score-1.png | Bin 0 -> 1237 bytes osu.Game.Tests/Resources/old-skin/score-2.png | Bin 0 -> 3134 bytes osu.Game.Tests/Resources/old-skin/score-3.png | Bin 0 -> 3712 bytes osu.Game.Tests/Resources/old-skin/score-4.png | Bin 0 -> 2395 bytes osu.Game.Tests/Resources/old-skin/score-5.png | Bin 0 -> 3067 bytes osu.Game.Tests/Resources/old-skin/score-6.png | Bin 0 -> 3337 bytes osu.Game.Tests/Resources/old-skin/score-7.png | Bin 0 -> 1910 bytes osu.Game.Tests/Resources/old-skin/score-8.png | Bin 0 -> 3652 bytes osu.Game.Tests/Resources/old-skin/score-9.png | Bin 0 -> 3561 bytes .../Resources/old-skin/score-comma.png | Bin 0 -> 865 bytes osu.Game.Tests/Resources/old-skin/score-dot.png | Bin 0 -> 771 bytes .../Resources/old-skin/score-percent.png | Bin 0 -> 4904 bytes osu.Game.Tests/Resources/old-skin/score-x.png | Bin 0 -> 2536 bytes .../Resources/old-skin/scorebar-bg.png | Bin 0 -> 7087 bytes .../Resources/old-skin/scorebar-colour-0.png | Bin 0 -> 465 bytes .../Resources/old-skin/scorebar-colour-1.png | Bin 0 -> 475 bytes .../Resources/old-skin/scorebar-colour-2.png | Bin 0 -> 466 bytes .../Resources/old-skin/scorebar-colour-3.png | Bin 0 -> 464 bytes .../Resources/old-skin/scorebar-ki.png | Bin 0 -> 8579 bytes .../Resources/old-skin/scorebar-kidanger.png | Bin 0 -> 7361 bytes .../Resources/old-skin/scorebar-kidanger2.png | Bin 0 -> 9360 bytes osu.Game.Tests/Resources/old-skin/skin.ini | 2 ++ 23 files changed, 2 insertions(+) create mode 100644 osu.Game.Tests/Resources/old-skin/score-0.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-1.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-2.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-3.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-4.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-5.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-6.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-7.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-8.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-9.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-comma.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-dot.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-percent.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-x.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-bg.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-colour-0.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-colour-1.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-colour-2.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-colour-3.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-ki.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-kidanger.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-kidanger2.png create mode 100644 osu.Game.Tests/Resources/old-skin/skin.ini diff --git a/osu.Game.Tests/Resources/old-skin/score-0.png b/osu.Game.Tests/Resources/old-skin/score-0.png new file mode 100644 index 0000000000000000000000000000000000000000..8304617d8c94a8400b50a90f364941bb02983065 GIT binary patch literal 3092 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 Y0OO;-v8NaCZ2$lO07*qoM6N<$f&jDokN^Mx literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-3.png b/osu.Game.Tests/Resources/old-skin/score-3.png new file mode 100644 index 0000000000000000000000000000000000000000..82bec3babebd59263dc486e5900a3a01ea4aaed7 GIT binary patch literal 3712 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 JPDHLkV1kmK?iT<6 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-6.png b/osu.Game.Tests/Resources/old-skin/score-6.png new file mode 100644 index 0000000000000000000000000000000000000000..b4cf81f26e5cab5a068ce282ee22b15b92d0df12 GIT binary patch literal 3337 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 TZ*tsI00000NkvXXu0mjfuoP_o literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-7.png b/osu.Game.Tests/Resources/old-skin/score-7.png new file mode 100644 index 0000000000000000000000000000000000000000..a23f5379b223d61079e055162fdd93f107f0ec02 GIT binary patch literal 1910 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-comma.png b/osu.Game.Tests/Resources/old-skin/score-comma.png new file mode 100644 index 0000000000000000000000000000000000000000..f68d32957ff8dc2e6e26e2b739eab85385548faf GIT binary patch literal 865 zcmV-n1D^beP) z8ION{U-N9C`MxinYv|$`M+}Q$F)W6~uoxD@Vpt6OzhDOaIXgR>o2*u=8V;)D@TF?C z+T17S{{H?ym;%w+TADXmhT?$-O-NWvN0;UR_GXqT)+8VcT7_OfX+F1jnFIZS?x8!# zh8}URz_+nygyLs1rfHeIXpZ(*xcNFpGu`( zwcG8nVHo$9mzUrA{eGSAow*&jOrP%XnP4jnsY0KQkB@6tS64o6^aq1MaO`wCezV#1 zPfkt<_8IyJm7o-H)Y71MrP!@^ySuwPg+f7WY;1^ZHX9uC`MfBVO5*6~=mXZgWiM7_ zg2J#BUQj3&i!V*nT&N{Y(}Zo?M9#738`=bqh+giU0>f>loV9|82+ zaU80#CVuw0IXW_vYCm)NDP(>JPj7d--Su=j9k?q&C<*&31~y-*}fwjTm!p*P&Jvpnyz z)m?^=o3|X6i423GymcH7s?X++lz!!LDcrGp4_y83T(4ux)LkG8*W#nj518@;Wz|pB rcm*5_XP0^3eKYf&7eE$5NN3S}(it^R$P@B}JRwiW6Y_*SAy4SP5!!0C zTE9FJKtC>>NlHB>x7U?fC3eMrUE}-6&6|p1Lzp5^Zgp4;-?Tka0Ir& z1rTBaEJ%Zoz;`eQ3$UbTh&3rgX9|7}x(Kd<2wyLO^E@RI`3er0F^e8jrdymkWwHaJ zU>EG6-At#`*8_n7WhgqG&Jb^YGairM;r=~O+gUez%_x%?%@8~chr`cux!mVouO}vx ziI~l1Vmh6Q;czJ0?Y5Q8W=ZG?xDRe~_LPX7{ta*sJS`LoBN8x8Qyg!)u8YB7AXw}% zxJ%jddP>*89q=fXO1TEF6vaC~s48xE{ zqtTROY?u4ELr$I4>-A5|~39QXWMIpY}VCkbsmq$cd@;G3M5vml{BBvrEa%t<@5R1rBdky#=iw0 zIjg12+aCmBcDQpy_7jQ3UL+DBYmj&PwOZ{J`d!cgAHWx$%}VBs{OkvExyH8F_z-XN zhAjBd4tYuwo)#^QA>$xAWkltmAR9EuCtu}5>6DRg%ppIcnq=Aa2%R;=oZ@xF169W7 zDr`CzH^QVBIE8JK`;M1drwJD)wp9Li|3LU5zyS0^RhH-3lmq|(002ovPDHLkV1mea BUy%R+ literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-percent.png b/osu.Game.Tests/Resources/old-skin/score-percent.png new file mode 100644 index 0000000000000000000000000000000000000000..fc750abc7e80287192efb65633e58b6b19e47125 GIT binary patch literal 4904 zcmV+@6W8pCP)>HPEA?bv`x0yR5sa^IhC4IP-&%2W!qCT)6~GN6jP8z zltl$(hYNRk=iGM=@9+a<{p0;+&Rp*D`+dJ>|D1Ea=v3#IPV=h$PLh+zA^z^}r3W$! zGSIW_KsG`4u0GIsjfJ#}={1b#=re=(4i|0#YNprjY=)m@0_q3k3FJZV@v;Kd0W|n>QZAbRG^nNiO?^UR0E#l_jgTq9~z?!8NzghdU+poJL2g+hsf zr%s(3>2NrVMMXvR`T6-ZbeqY?$H!~YqD2vtCQb6+wr$(ljEsy-_`CopALtxOAAjEj zYuGe?=SEiIO=||zdXeGb;n7b$^;GQbx8L61U@)iw0|q>G>#eu0fbXu5AobnyNm!iC z4z)E9C>-cIe}DhP6)RT!WB&a4{vIA4N~hB)tJSLZ?Ahb^^2;xA!^W3iepw$D7N$%l zlbSejV#3_Hb7Ph-U;alyK|vN-05e)gekv`XA88NQ97OAmNJ~pgUb=LtH!!Mj-IkUX zjSNns|XW)x3H0^tau1n;SBX zn?MHf>esKInlWRBY5Vr=6Ysg_p1_kQPX@rpK|sYoRTMCIU1YF7xdbv8mYSNH^vENR z1SBLRC|uKMG%Balsp{(L)P@ZkPK)5?=uYJ3;9;i4!hqsIgHtwc+&E|Y^yx<2I5HzI zWE2-FuvbA>_Bc1})?TD*91RaRCO`cV;31^JRgM9}_V?m^S0O^aH#Y*}zzT%68g zvABfB&uwmQR^WS%l$4au;q4xv<76FG@VTw4nq($@$l0SFeDJ|3@X&S)+U<7L(9obR zT)3dX9M#ydW6_M1o10sk-?_WHtEi|bwPVK)pC5nxal+1>J4ff`miEGX(4U%`nq05AY1g8_2nq&;fsEnr zQ2Y3bpW%0`#RCTps3}vXd=GDT0Uf4*hJnW_qjhJRG*S3QMnje?S(4!E>)VDwyc!!D zReE|lGMG=%(|Yvi(Sbkw@IxZ_uK~=i)i+zSJD4+W8cANrELsIJhMFoVDN)~j_nrFn z*I(7<&70NW!Gm4DBQ0Vo$^*JUDGBp%OII0GBFb^v7<|huw|JS&<~BdV4OLZDsWofX zoQJoVoN_3L1ikalJI!a#oSE?ItFNli(9kx*vDs`cM(}=S3_n8+6&4oS_U+r(yldAk z=P$qff>B+qUcFkOfKk)7lu8+O)|5 zzIY7Y{t0x3g2*6X-n4b=R=?S^XGaeiGNcWYcpoHjmlc3kTPiCnFJFQSHeYt-%9SQs z8pH|t~*}3o*C3~41znaK}Kr%Bk?P!W^q8gaph!G?3&%c4t3bKg) zB0(YZEr4n)vu4e5`7Q>ZnwlEa;sv3_ik@mw1|Aps04W-@riwlVzGD+uw3;xglR`O~ zT@z^B4rI^<9(bgc@+4Y7E_lKK39#2FM#W>fwhU z_C*NCxcKLve{T8x_ur5Al@^tzbaF6UlNv!k0}NWu0R-qnZ@lrwfvgb;1OTcvLDDaw&AO2 zbN~JK>m)wK`w_8h*|H^%Hrz~t8Vs=QI!OG%oH9_0APDrulN3><7Hz5zE@l!V*v0(( z{8PJk?`}fL@`V}#O${Q(VzAx>N+gPZ;DHB*vDry54^h&(fB*iY6g-^NoC>?^uDcRP zjT)unNQ^>-#9v2REYxVx(N|11(?ikG^Im-M#RE_)I@vd|ℜ1r5i{-0$U=)+!&zI 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-bg.png b/osu.Game.Tests/Resources/old-skin/scorebar-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..1e94f464ca497c1eb0645586af13f489c28bf6f3 GIT binary patch literal 7087 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 Hu0mjf)F0A( literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-colour-1.png b/osu.Game.Tests/Resources/old-skin/scorebar-colour-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7669474d8bcce7afd6a0bc8c247b2c0ccef46c22 GIT binary patch literal 475 zcmV<10VMv3P)p000dL0ssI2y8t*G0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzeMv+?RCwC#+uKfqKoo`HwP^v37s}Mv z^v!%U1!Ihfi4kX}CPfX0X%(^kzi_c2n-*@mIwiGMK`l9bAd|@>+2kxH3;8PBW#@C; z{Cu^Jc@@viL-4zIex{JtVGGC4)cTyOZsjiG@m%+Q=2AMB3N?PF(pxDjRlkO;GHmsW zDi?wks-NXH;lwsq^CWj2U&p`iW6Y~g&f;}G#QE*L)Y`aRc23sGTTLhT%?uwWlgXpm zbdw3dY%FoWz8$vl;lR0=*8l*=7DjA@#JG(ZmZjD|Aj#jH%Dj8Vb z0@~6~q?G|y$p8**XzN)n_m!^o7qL;+0F?}&zc{Cs002ovPDHLkV1lE5)RO=J literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-colour-2.png b/osu.Game.Tests/Resources/old-skin/scorebar-colour-2.png new file mode 100644 index 0000000000000000000000000000000000000000..70fdb4b14637abdf6237c4bf883fac33542d91f1 GIT binary patch literal 466 zcmV;@0WJQCP)p000dL0ssI2y8t*G0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzbV)=(RCwC#o83->Fc^m45BKBF0C$_a zGcQd`G{%^iSZSw`Wa%hu62#{T2XC9NWt{Nvv0z?bIH@L+$vN5NEM^PGL-C!9SaTCX zJA5o+j>AyD4&h@JYqruV9Br;{rFG10%`RlWm220Q(nhwE^D0YOyQH0eS6zqTH)`Hg z?PZ&|_Eq&>KJL@JgsgaK_uR=(F&}?t6ZeTU?k78^yG}mNTPLS#&d-y{}3GHw0Ev%NR~I1fq&n1`AM`PHtbK|Bh8k^vBQXJ~o4HM|P2SiJ60GQj*AXdAu| zD+ACa1Hh$*wnV#}lCGsiY{(h_B?CZOoVk+hwQ{%8GWND>UJwAFWPn9@I%QDH(4Zwu zpDAwy41kgW;6n9f343z8qHO?_4DbgPo7n{Gh`%H^(FVYC}$xlkqqJN(O*oGR(I!04(ILP<;w80AoP?p8+Di_5c6?07*qo IM6N<$f~e2NAOHXW literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-colour-3.png b/osu.Game.Tests/Resources/old-skin/scorebar-colour-3.png new file mode 100644 index 0000000000000000000000000000000000000000..18ac6976c95a162914d6cdd371abf71aca48c72a GIT binary patch literal 464 zcmV;>0WbcEP)p000dL0ssI2y8t*G0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUza!Eu%RCwC#o54Zf&9a$#fb!hdqhv6oME>*b-I@j97I{xk2~Vy&efRX{Q>@;ykS-Ib75%;$L+zCG6SlinakzGQbTgHgX8=7426~>R5c$-xvUZk^$zC(&YaZTe{a# zA^=JTfQiHL1ixC!gJLos2SCXHkS4=;D+9nn-U{WH00RIwRsWn@QMIQ40000Sq~Hvz@uty#o9-zf7^n!)!M^gttV;{u*g+| zB8LZr8x$oRIrf_Q|K4}r+04woGrJ3k+3nxW^FA|sWOsHx^Zi}#u?qe>Zb}EekM{M6 zNRQ)IjuM#mHPEg(UZsP8YUvJEN#o;Gj^Z_LfV87{&DTIX;24!8_Y+lQBKs*1RY_Br ziEd@@A%m6ZHszrzX#&%}uOzUah{-i?&?$iS0$3>}dQKsqwRgI(Al!B_H5lk60k7?_1f$F4C zJ4n^`c%q6;8K}NAh3SzrJ6I>EnnZ!B_&;M`u_;q^xF{48NF^1Z`qBiZ8%U1};CMh) zTa#TxZeUXmsxJ*;dL{c_7pUg#^UsPl(`_k6rb`(sfW;}l{wX=*58Z)*J zF28aJKY`Evc_ZFkvB!9dnc_@pb4=h8&0`@^y`%ZfR<BTuGcz-* zpr9Zl91d4!WMqub%E|&ghP&rX!ku?dKy1f}dw1u=NxkS|h+tXWXT~v?ina`i4MaQY z^K;+4V22CJn+7nWevK(JFE1}M5{Xn3$TM?ub3sDItv{>4ZFf$zRrMk+HF%XCZLbqd zoYa$`ss#1(uN-PeTS;#kz%&#}Ld;~Zo12}TeP&@{A;@=P;Eo;m`_c`#=*n^E(IZD< z0iFD4!~%Qbq+$Vee_BKJrU6W@P*SIt1waIId~tCxN=ix)3^n1Q&zr!?1}{gvIRAp- z^g$sLP;*`1Y`wjvyk9dZwTS!TWp!W3pn5t%Q5_D|n+7mh^U!S`nc4A0MMdb-rw>M+ z(Fcc?EaSDiG1R84*@~*UmzrV~h5xGpKecRXIb!PR>x{N6_&9l#R{U%}!LF0$EuZ zL`X3ORT0dY*Pn0io4?(jr_L8a+Oga^t?Nxmb(l<<5B zZbj5SJ@6(93l%I{^fIY&DYn%%;79a$CAC7^7ibdYrL0*KaL@h2=c_YEab|LydyH*c!L^!xo>nRBYAqO$UA%$fZ>Zf^LXqgJTd0%}XjL-nR2%#4f-Zf2P>tEw-=wby-&(<-~w zP@BYIo=xVK^}HM;A}uBh8i|2=RupP$N<;Ogs?TJ~oKrQ$6{cZ2hu9vg75?#BYIm9* zZxpKBG;Iui{^(TPaAO(W`;tg7bJ}WBr?qKOrDh7Kfs}>nO%<4|0>ogl+H65V0l~Zg z-@Bff**_A@8YxWsZ4tGfFn?Mx8nunP@HXw2i09@Cz}Gx@4Hi6hJ_Zg9)7jroO{Pw> zAGH?vCr-)`P=hH6)tf3XsZAiDVCl*1>hmz-v{F>ft|6cKuedN53FRr37h}v7Q}DN^ zp64*jg<9EjMKh=4`G3vEIp_AKd;3fJ^Ggx@c^dDAwoToTK24)@ye^e zM`Pnoj2V0`1`$j)(3a(%k5YSt8pHJm0kuMGS&#S=3*VQ#WHA@OCn#xM6>s}|Ix`56cuoQ zdfnG3)N8`bIuA_H?Dxs+o=M*%LV9{<8Jx_o(=FaII!dzUHy+Ss=>w4x&=YWjGJ|UIZ6z6c3S+)Y}GSF zr*tlE`TomjTUg5jow^%-|SQJ4|MF+4j+20t+8=gc>{sr8+GSz@d8Ms8crCHqjC724EY^ zkz$au?6~anZ`;2cJBafyD90)amS(Kqjah9$G*XUP)VPboG>QwA5B`+$Suf_y{xuFR zei4rAWh0qarla-G{1X-LzK=x&ywVo!G6VXqP`BUVjItcIvAjDjoR-at_RQp}YP*@` zKwV%Oz}fcnk-B34v(^!+mIPEt6zLcgx%o{eb=rd;`}71G%n`V|YIvL=O|bMu=+W#&jm++L-{Dw^CH|Su3&Bf)oL6X-2s>j8D`Wzx# zbFVxu{Z+1Vnx@CMb^&D^buLbMATrvu=6)b2K3>F>M4DB z6BUj7u+>z?t8mCU8R&-6V%#)yEIz(*D7qDbSA{fe<6UKnL!`|9Z~fe%k zDSh7{m_y(^%c}kDSd!n=(m4-T=ub~j#JvKmWj))2_Oyx?vgg;40w2dY(^Odv<& z`ROO{d=l7A-?oh!8^ReBK~C(AMYlK2nt&cXzeFf}fLaSeAu_dLYrYwy&~PkFV_(y( z`S{N}^x&}fs3k>j zymv4`e4ms_elrvxl}4h9l&R9mj~b2pQ%wLBP^Vv^KmOO}EKr$0y~}o9Wl-bJD)%Lz z&P2f4U=AXfWpQA_WMzD0Qr9Il#H~pb;un`5Pavn^`Wq*ru&`CYY@zL8YAo$IBrfC( z0XFtin5n(t=FV(J79|0|_Mqt!F+@keEF+i$AfLf5D}3v<(yJ?n^WY|t8#AvX1c=hc-FWqm z$5B>v8Olz)4cA@!0;%wG0Z=g&XY@1N)YKYdu32Si+Qq#IBEbwEK`HPX;#@i{M5!QwW!M#m=vL+A>DsUQWaxs_m}p- zNYkw5m))1OfBY4AX^G`)jua_O(3HxA9R@tvOM;dOIBxxHPI8}OykOk;ZUnQRg9`16 zG}5ks7%|X{Ge+f6P*TD3N~f|M%MnXvork4w&mdpPOzk)xQJ8aqO5t|{3*X1ni$3EQ zjTY@i(WGi0KcPPXJVHQQNSUFiI+@af0cS-TstpBWRchWhE7RQA<5TErEIY|^NK;${ zhj@(Qb#TYKMf3QpD|_0?lR<|%wEZ=$yk5#iuHSS2sids29BPxv95Q=eOS$CBD{sL3 z`v=kGxP|-5k)ukuDrGkoQ2les9+(%|)FcLD8?`=tb0`pM6{a>!5H%g9Xn}yl)EdAg zFcWEE(U|&2kBvE}i7u%V1G@v)DKakt;kHh{y^5vqenv%f9zXW@Fw7yFd*Z?d?(e2f z8;09vo{n?Q?Lp6f!3TEfhn+>fa;r2Kg{TZoncA0I08I;Xv)A;68e`6x$^GHbA_DRm zDKtVbRer+lWju3G6k~2Bxu-b@(rhM|e|yt@qyyK^@FmfU?o|3@V!)sN_!%m%D~wZ~ zbXt?2^*4s}lTQ)q{QLV+8_LrRU132EQy<-~iQ+>(DX>BRaEY&F%6@QK4@y0@&Z0W^1u}Wz0F%66Byc6&CCzBrOgR zqy0-%I{4g=d#ZPmAMNF+OwDnfenfq$BB1L1pgAJ$GE&`aUVs3Kv36A$b&G1HkughW zZoG3ma&yDnj7Gv_ZdI}Y5xEtML^OI`gqzx6kS?u|(jNRop=z!viUHA*dx|Z$UpA=5 z_VrhQPd~97&c7Ur@$r6YM2lx~-F_v(h^$zY^Gj;CvKQPp|1)^JD(5I+ zAfGVAW??UV{c}9Ia6g${O%xy6OUeC9`_TP-{YsJ)2^N(ehq@mOC%?FfgJLI{6)z$Y zj{{Iu_lF_oFZI_&!iudj#FVS@t)*}vQK!Y+nxNX%`Q{rGz%8{L$}dfY_(XoGBCg4m zjahN(xHR@zdVHFZi{4YcpFe$9Rbdj>hS5Cy`)q3r;RoN^8turAh1MADVX@y>)O+Ji z7odCh0<;A)No{^oS%ha>&I+YVD69uL;gClURw7;;I!s1C+at<<5i;K@H_u_Nc=t5L ztV)@geTzWWy=6I!H^sS(_X`!SF47D4AGL8?sNdXJ^goCinoU@G|J+=41EH zEUbQhqvb+iIqUF?PDWu-Gg@1l5NK*07nhp6Xps`DMT45s|&_tRHF6-kfYlj*js2V}_IaG|SkTuisGhaJ! zpcgm%xHk&M^u)T}g_yNBjKxg>-bFUV#ZTPK^3`p7G4F+S`0#T-ndcI8D`0ax^RR2D z7i<2yJ_hKiQ-`6jdn;NP&;SGKr2tAnw|9bgvZe+72N$7NNggTPOF%p#XwvjZE1p{*^(Em_{&|0owhm zfLvc|8M|x1tdZ zaRsu#=*;oOSUs#e&uiIxy!v%UVahUyWE!7 zOJUB3^UB$(i-Ix(^;V^-La8dBKEOtu%LZ2ITMwy{RG{Y4mZd;_ zaqqk>Q2wyOVlIs$0t>Ha{`yu_zOsV*SG$ib5f$UrIoarWToGFS^GVcXv*&)f{}4`@ zFq|OK>)J@6!3bBO*PD;Lykb1JVi*5h=6gR~zZ>&wHXyfqUz|)Ahssr|iOz79%&?{x zGKBABAXmNX2s4=lA?*Q?V|NFVEC9!gid~@UM>1}FY~TKZ(GN?I zO^O^s$_#T;%04M0qlkR?AiTEeFb3~Ez%LXQU9-6}weM}i2`>S`pZZplR)OtD|^yE_rpss zDwsXX3C}+JCX@wlaDP`Tc`bu>>!DWMao4$cdF@V|wCjuL zYnWMnV)IVyAhSF2j3K-_y1jd*hVmoZT-3yd~ib_Q;^X&|mX^C@(%+seneGsSKkU5eXx zHDR;Ktg=NlaxOj@r=8vn+1WYx)tj3vDrJD4XxM^v2Q$#K_ym;pFC&+h& z8$XGu0~ZRl{(AFg$So^DPA>EPtpqcK|E$s6V8&BsBBSM{V7qj((ob&(ZuyBqiu96d zv*?I&DZuDXtq=b4=K%h=cnfYN1yo9Ewf~-eIxfAeH_A?GL1W`StXbWJ+`KGQOgsS| zZy{Qmv&qEoB^ZrZyJk1e{niOMXYz6Ab9_EBeBJTH!p$gs;k}sgxr;<@T5B2`a*ab( z$~@wsdU$V4nOcPUx;^AK4|A0!sLW2AtmI}kJ8e4QkALRD%v&-<&q@)&EWxjSaS(rg zY(sRN1iP6H&lrWvuj);?Z2=`m;!ces=u}EV!eZx#W$RfWO47E|P6Cg|A zC3D)1{NwTX-IE{VvRB@Bg_X#SeW8_;2g9GGpq4J8BS(%HK<%@+Lvhj+ryu=s`YqEK zvzy3-UnBq>am=q`D%RGS&F|`!9*QP2Fygdqyz-KQ`Bi&>f9!}E?Z;}jtm8Z9su8$- z)<6QX8!fGS$Zs_fQ0BYZc$&kufwpF}1`eaOypr*utW+B9k%ib zTQTfAm-+GD@Ah*OSYa!2G84G8Zy_$bVlb|pQA{9rQ+lzV;2q{L^{EP)sf$a|t2q>{ zvZ&eU{^EnCRWBdJ?e$X7ldT6mOJ3N8)z7cuIc`M~1Jzhn;K;}_^y%9ZTemc0-@bh~ zbm$NZjOr+_mci`wbr!mO(G0fICR>{*FE0<{&+=l+-}Xcc2kfMc8r=s|r=5t&-|m67 zz!w4}Q)M$Lp^Xo>H1}1?hFe-j9H=Y+^HDnFM43pKMo!|$Y9npm|=F6b-|(;iOwAA^tEu= z>R}A(ygE z)_qCQ?3Wy*FtsdxpWoKj7C{%S)ERFeQl~>@OOG?C)W$Q@%i5JQ{Fr*NR}>uBfad@~ zWoitF$wOh$)1g_qtE?joVkZrmmCGtT%>07DDjq+4-fZ&A`N+(qC^#dJAm@Z;C>nt7MYNAk~s}h{9ATrBaO! z?l4)2Ai)e!3v$Tp;$i_19s5094KtY`n_r1i+0S>FQ<^KT0xp@3=&%oaFGjq1CW%a8 zh?o=$&BMnYY2vUoO%Y8Wro!saAFfxiVZDBFvHOx}wo4a8KGx+`J8lptKcm)^<^%DA zM}vpDfQZDOO|6=PCDmc{yD@GkqH;CrCN{)Nd91Ph57vbVXgf@#;Of>Lu>f*M-eU69 z57+BY3>xe_Co>XpnN{ubyromXVb++@YqD_zuz91jHk(pQm3Pz<<(&$3~hVBzZGnezYm|VID2eE`hp9sH3bbd6-V>G{44Bq+OltbJGRpu>kE7sO-eD zZ#ZCJ;xHZ67l|E$X}_OtFpm{zmq7jaBmJ9}o|t@)WPgp_ZZc#;-?)FNJ18fZcD4Su!0afXT>^E>r}`89`nM0Aw1bQZaMaj&Cx*}bZ-Cj6Ksy_% zwCio_76G-N+!vZvXCCbAG=*XFCP$c&1Uja>9A-xY?M$fVb$KkqS+;G}U(oji=aopz zQ|6tf$h!nUX#=w}fOZyC`(lL_sN1*cPaNMzJ`2olZtXx_^31e?*;zn418UrXXFImj zgGww^yMMJ$^J7Je1YvggkE&0Z2F>=_DGT(g2$W!lx^svAVDIB&XH(16$zX1l!wlOL z+2JrRxDXn<0MiNRR{=Ffsk?USPxLCW2154ecEgA5)h7p-ZYK7aV4gn(n#ZGClyn2y zkx*re6>2i2?%t)JEDluy^urGb=7t!U^CZd)L%!7MF~MZVW&qushCsiHltvR!qEi|7 z?A8JH?rk-S|>mzu2#@EWKm9 z<$u`g=Oo}OI~1lzEcL*)wl2A}0@~5$*RD{bv9$_Hg$YJWv+h?J*wz-yd%SLf=?3wb zVV-w^?lTRDW|O;VW9OaVu7sM%a28{^Yx~d^`@ZpdHZJ>i#IBac06awP1;j4+5OLX8 zQz6exMW9CuRkom{tQoH}-eWSg;o>vMgW?qv2-%7rPEAd9m|Y1pS*VH)md632d8@#> zIHmans}4|A7m(&P4pI^((4lMn9djBYTaQ(B0AgPzz>D@}yzPqzJN>_1ZIJ}_wN!@L z)j*Sh>M_B!12wP7?(|cQaQI&}iI(y(Qvql)P!)%$)(pq(cQ>7)V%Zu7iGWNwm|g8d zkH)9Uep5!a5SH7L-hh1hH5tHov!_!EW~u>A2CCD#NluII%W_!F1+4u!qyx-U1nLGA z2|{&R`$48qH?VGsOi7rj3N#t0KWx&U=LP9@3v@t|l&@tUuJ zcGB@Cy0n0ngksYQWV!-9n&VcE0gm?Hd}97Td=D+%PsINTFaT0zMM56CZ!iD=002ov JPDHLkV1iG9rlSA= literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-kidanger.png b/osu.Game.Tests/Resources/old-skin/scorebar-kidanger.png new file mode 100644 index 0000000000000000000000000000000000000000..ac5a2c5893b635520d3f8e3804ff87101664d591 GIT binary patch literal 7361 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-kidanger2.png b/osu.Game.Tests/Resources/old-skin/scorebar-kidanger2.png new file mode 100644 index 0000000000000000000000000000000000000000..507be0463f0732a07aa7df87cd57c1f273c7d55b GIT binary patch literal 9360 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 KMNUMnLSTX Date: Fri, 16 Oct 2020 16:29:10 +0900 Subject: [PATCH 269/350] Add support for old marker style danger textures --- osu.Game/Skinning/LegacyHealthDisplay.cs | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 7d9a1dfc15..0da2de4f09 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -84,17 +84,42 @@ namespace osu.Game.Skinning public class LegacyOldStyleMarker : LegacyMarker { + private readonly Sprite sprite; + + private readonly Texture normalTexture; + private readonly Texture dangerTexture; + private readonly Texture superDangerTexture; + public LegacyOldStyleMarker(Skin skin) { + normalTexture = getTexture(skin, "ki"); + dangerTexture = getTexture(skin, "kidanger"); + superDangerTexture = getTexture(skin, "kidanger2"); + InternalChildren = new Drawable[] { - new Sprite + sprite = new Sprite { Texture = getTexture(skin, "ki"), Origin = Anchor.Centre, } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(hp => + { + if (hp.NewValue < 0.2f) + sprite.Texture = superDangerTexture; + else if (hp.NewValue < 0.5f) + sprite.Texture = dangerTexture; + else + sprite.Texture = normalTexture; + }); + } } public class LegacyNewStyleMarker : LegacyMarker From 8104bd0f74b0874d6cc5f38f6aec0479aee4587b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 16:45:28 +0900 Subject: [PATCH 270/350] Add fill colour changes --- osu.Game/Skinning/LegacyHealthDisplay.cs | 76 +++++++++++++++++++----- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 0da2de4f09..f44dd2b864 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -12,14 +12,15 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Screens.Play.HUD; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning { public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay { private readonly Skin skin; - private Drawable fill; - private LegacyMarker marker; + private LegacyHealthPiece fill; + private LegacyHealthPiece marker; private float maxFillWidth; @@ -63,7 +64,9 @@ namespace osu.Game.Skinning }); } + fill.Current.BindTo(Current); marker.Current.BindTo(Current); + maxFillWidth = fill.Width; } @@ -82,7 +85,18 @@ namespace osu.Game.Skinning private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); - public class LegacyOldStyleMarker : LegacyMarker + private static Color4 getFillColour(double hp) + { + if (hp < 0.2) + return Interpolation.ValueAt(0.2 - hp, Color4.Black, Color4.Red, 0, 0.2); + + if (hp < 0.5) + return Interpolation.ValueAt(0.5 - hp, Color4.White, Color4.Black, 0, 0.5); + + return Color4.White; + } + + public class LegacyOldStyleMarker : LegacyHealthPiece { private readonly Sprite sprite; @@ -92,6 +106,8 @@ namespace osu.Game.Skinning public LegacyOldStyleMarker(Skin skin) { + Origin = Anchor.Centre; + normalTexture = getTexture(skin, "ki"); dangerTexture = getTexture(skin, "kidanger"); superDangerTexture = getTexture(skin, "kidanger2"); @@ -120,39 +136,46 @@ namespace osu.Game.Skinning sprite.Texture = normalTexture; }); } + + public override void Flash(JudgementResult result) + { + this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } } - public class LegacyNewStyleMarker : LegacyMarker + public class LegacyNewStyleMarker : LegacyHealthPiece { + private readonly Sprite sprite; + public LegacyNewStyleMarker(Skin skin) { + Origin = Anchor.Centre; + InternalChildren = new Drawable[] { - new Sprite + sprite = new Sprite { Texture = getTexture(skin, "marker"), Origin = Anchor.Centre, } }; } - } - public class LegacyMarker : CompositeDrawable, IHealthDisplay - { - public Bindable Current { get; } = new Bindable(); - - public LegacyMarker() + protected override void Update() { - Origin = Anchor.Centre; + base.Update(); + + sprite.Colour = getFillColour(Current.Value); + sprite.Blending = Current.Value < 0.5f ? BlendingParameters.Inherit : BlendingParameters.Additive; } - public void Flash(JudgementResult result) + public override void Flash(JudgementResult result) { this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); } } - internal class LegacyOldStyleFill : CompositeDrawable + internal class LegacyOldStyleFill : LegacyHealthPiece { public LegacyOldStyleFill(Skin skin) { @@ -175,12 +198,33 @@ namespace osu.Game.Skinning } } - internal class LegacyNewStyleFill : Sprite + internal class LegacyNewStyleFill : LegacyHealthPiece { public LegacyNewStyleFill(Skin skin) { - Texture = getTexture(skin, "colour"); + InternalChild = new Sprite + { + Texture = getTexture(skin, "colour"), + }; + + Size = InternalChild.Size; Position = new Vector2(7.5f, 7.8f) * 1.6f; + Masking = true; + } + + protected override void Update() + { + base.Update(); + this.Colour = getFillColour(Current.Value); + } + } + + public class LegacyHealthPiece : CompositeDrawable, IHealthDisplay + { + public Bindable Current { get; } = new Bindable(); + + public virtual void Flash(JudgementResult result) + { } } } From 9572260e6da84c1a4b762a5a16376e4b4fcaafa0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:09:00 +0900 Subject: [PATCH 271/350] Add bulge and explode support --- osu.Game/Skinning/LegacyHealthDisplay.cs | 114 ++++++++++++++--------- 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index f44dd2b864..fece590f03 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -96,32 +96,25 @@ namespace osu.Game.Skinning return Color4.White; } - public class LegacyOldStyleMarker : LegacyHealthPiece + public class LegacyOldStyleMarker : LegacyMarker { - private readonly Sprite sprite; - private readonly Texture normalTexture; private readonly Texture dangerTexture; private readonly Texture superDangerTexture; public LegacyOldStyleMarker(Skin skin) { - Origin = Anchor.Centre; - normalTexture = getTexture(skin, "ki"); dangerTexture = getTexture(skin, "kidanger"); superDangerTexture = getTexture(skin, "kidanger2"); - - InternalChildren = new Drawable[] - { - sprite = new Sprite - { - Texture = getTexture(skin, "ki"), - Origin = Anchor.Centre, - } - }; } + public override Sprite CreateSprite() => new Sprite + { + Texture = normalTexture, + Origin = Anchor.Centre, + }; + protected override void LoadComplete() { base.LoadComplete(); @@ -129,49 +122,36 @@ namespace osu.Game.Skinning Current.BindValueChanged(hp => { if (hp.NewValue < 0.2f) - sprite.Texture = superDangerTexture; + Main.Texture = superDangerTexture; else if (hp.NewValue < 0.5f) - sprite.Texture = dangerTexture; + Main.Texture = dangerTexture; else - sprite.Texture = normalTexture; + Main.Texture = normalTexture; }); } - - public override void Flash(JudgementResult result) - { - this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); - } } - public class LegacyNewStyleMarker : LegacyHealthPiece + public class LegacyNewStyleMarker : LegacyMarker { - private readonly Sprite sprite; + private readonly Skin skin; public LegacyNewStyleMarker(Skin skin) { - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] - { - sprite = new Sprite - { - Texture = getTexture(skin, "marker"), - Origin = Anchor.Centre, - } - }; + this.skin = skin; } + public override Sprite CreateSprite() => new Sprite + { + Texture = getTexture(skin, "marker"), + Origin = Anchor.Centre, + }; + protected override void Update() { base.Update(); - sprite.Colour = getFillColour(Current.Value); - sprite.Blending = Current.Value < 0.5f ? BlendingParameters.Inherit : BlendingParameters.Additive; - } - - public override void Flash(JudgementResult result) - { - this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + Main.Colour = getFillColour(Current.Value); + Main.Blending = Current.Value < 0.5f ? BlendingParameters.Inherit : BlendingParameters.Additive; } } @@ -215,10 +195,60 @@ namespace osu.Game.Skinning protected override void Update() { base.Update(); - this.Colour = getFillColour(Current.Value); + Colour = getFillColour(Current.Value); } } + public abstract class LegacyMarker : LegacyHealthPiece + { + protected Sprite Main; + + private Sprite explode; + + protected LegacyMarker() + { + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + Main = CreateSprite(), + explode = CreateSprite().With(s => + { + s.Alpha = 0; + s.Blending = BlendingParameters.Additive; + }), + }; + } + + public abstract Sprite CreateSprite(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(val => + { + if (val.NewValue > val.OldValue) + bulgeMain(); + }); + } + + public override void Flash(JudgementResult result) + { + bulgeMain(); + + explode.FadeOutFromOne(120); + explode.ScaleTo(1).Then().ScaleTo(Current.Value > 0.5f ? 2 : 1.6f, 120); + } + + private void bulgeMain() => + Main.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } + public class LegacyHealthPiece : CompositeDrawable, IHealthDisplay { public Bindable Current { get; } = new Bindable(); From 77bf050a80733bba4b3a5a6434dc66dad933555e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:24:43 +0900 Subject: [PATCH 272/350] Ignore IgnoreHits for flashiness --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ac74dc22d3..c3de249bf8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -319,7 +319,7 @@ namespace osu.Game.Screens.Play { processor.NewJudgement += judgement => { - if (judgement.IsHit) + if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) shd.Flash(judgement); }; } From a1892aa0a7472605ea389bc48db9a465d484f4ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:24:56 +0900 Subject: [PATCH 273/350] Only additive flash explosions over the epic cutoff --- osu.Game/Skinning/LegacyHealthDisplay.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index fece590f03..489e23ab7a 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -18,6 +18,8 @@ namespace osu.Game.Skinning { public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay { + private const double epic_cutoff = 0.5; + private readonly Skin skin; private LegacyHealthPiece fill; private LegacyHealthPiece marker; @@ -90,7 +92,7 @@ namespace osu.Game.Skinning if (hp < 0.2) return Interpolation.ValueAt(0.2 - hp, Color4.Black, Color4.Red, 0, 0.2); - if (hp < 0.5) + if (hp < epic_cutoff) return Interpolation.ValueAt(0.5 - hp, Color4.White, Color4.Black, 0, 0.5); return Color4.White; @@ -123,7 +125,7 @@ namespace osu.Game.Skinning { if (hp.NewValue < 0.2f) Main.Texture = superDangerTexture; - else if (hp.NewValue < 0.5f) + else if (hp.NewValue < epic_cutoff) Main.Texture = dangerTexture; else Main.Texture = normalTexture; @@ -151,7 +153,7 @@ namespace osu.Game.Skinning base.Update(); Main.Colour = getFillColour(Current.Value); - Main.Blending = Current.Value < 0.5f ? BlendingParameters.Inherit : BlendingParameters.Additive; + Main.Blending = Current.Value < epic_cutoff ? BlendingParameters.Inherit : BlendingParameters.Additive; } } @@ -241,8 +243,11 @@ namespace osu.Game.Skinning { bulgeMain(); + bool isEpic = Current.Value >= epic_cutoff; + + explode.Blending = isEpic ? BlendingParameters.Additive : BlendingParameters.Inherit; + explode.ScaleTo(1).Then().ScaleTo(isEpic ? 2 : 1.6f, 120); explode.FadeOutFromOne(120); - explode.ScaleTo(1).Then().ScaleTo(Current.Value > 0.5f ? 2 : 1.6f, 120); } private void bulgeMain() => From de60374c88a5521cfeeb6d5d0d942b0cd1a719c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:26:14 +0900 Subject: [PATCH 274/350] Remove unused using --- .../Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index 181fc8ce98..e1b0820662 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Screens.Play; -using osuTK; namespace osu.Game.Tests.Visual.Gameplay { From 05f1017c282317d848265d15564ed8e48c7582f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:35:21 +0900 Subject: [PATCH 275/350] Fix lookup check not being updated to use prefix --- osu.Game/Skinning/LegacySkin.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f5265f2d6e..06539d0f63 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -325,9 +325,9 @@ namespace osu.Game.Skinning return null; } - private const string score_font = "score"; + private string scorePrefix => GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; - private bool hasScoreFont => this.HasFont(score_font); + private bool hasScoreFont => this.HasFont(scorePrefix); public override Drawable GetDrawableComponent(ISkinComponent component) { @@ -351,7 +351,6 @@ namespace osu.Game.Skinning case HUDSkinComponents.ScoreText: case HUDSkinComponents.AccuracyText: - string scorePrefix = GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; int scoreOverlap = GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; return new LegacySpriteText(this, scorePrefix) { From e9c4b67cf4688154c1b044b20335a772103e996f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:35:35 +0900 Subject: [PATCH 276/350] Inline variable --- osu.Game/Skinning/LegacySkin.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 06539d0f63..22ddd45851 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -351,10 +351,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.ScoreText: case HUDSkinComponents.AccuracyText: - int scoreOverlap = GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; return new LegacySpriteText(this, scorePrefix) { - Spacing = new Vector2(-scoreOverlap, 0) + Spacing = new Vector2(-(GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2), 0) }; } From 3ce6d1fea103cf3c3d96df2f77684ffa6964cd4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:36:15 +0900 Subject: [PATCH 277/350] Remove unnecessary AccuracyText enum All elements use "score" regardless. --- osu.Game/Skinning/HUDSkinComponents.cs | 1 - osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 6ec575e106..cb35425981 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -9,6 +9,5 @@ namespace osu.Game.Skinning ScoreCounter, ScoreText, AccuracyCounter, - AccuracyText } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 6c194a06d3..27d5aa4dbd 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyText)) as OsuSpriteText ?? new OsuSpriteText(); + protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as OsuSpriteText ?? new OsuSpriteText(); protected override void Update() { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 22ddd45851..cd9809a22b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -350,7 +350,6 @@ namespace osu.Game.Skinning return new LegacyAccuracyCounter(this); case HUDSkinComponents.ScoreText: - case HUDSkinComponents.AccuracyText: return new LegacySpriteText(this, scorePrefix) { Spacing = new Vector2(-(GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2), 0) From 24b0a1b84b75b4ea10b92e54aaa6066b08e4452a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:38:21 +0900 Subject: [PATCH 278/350] Switch to direct casts (we can be sure LegacySpriteText is present at this point) --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 27d5aa4dbd..a4a432ece2 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as OsuSpriteText ?? new OsuSpriteText(); + protected sealed override OsuSpriteText CreateSpriteText() => (OsuSpriteText)skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); protected override void Update() { diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 41bf35722b..39c90211f2 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -31,6 +31,6 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as OsuSpriteText ?? new OsuSpriteText(); + protected sealed override OsuSpriteText CreateSpriteText() => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); } } From a774de2270c722de1d00c4cdef37a7d9f38c2aeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:40:15 +0900 Subject: [PATCH 279/350] Also add support in LegacyComboCounter --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 3 ++- osu.Game/Skinning/HUDSkinComponents.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index cc9398bc35..4784bca7dd 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; using osu.Game.Skinning; using osuTK; @@ -246,6 +247,6 @@ namespace osu.Game.Screens.Play.HUD return difference * rolling_duration; } - private Drawable createSpriteText() => new LegacySpriteText(skin); + private OsuSpriteText createSpriteText() => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ComboText)); } } diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index cb35425981..c5dead7858 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -8,6 +8,7 @@ namespace osu.Game.Skinning ComboCounter, ScoreCounter, ScoreText, + ComboText, AccuracyCounter, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index cd9809a22b..db7307b3fe 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -327,6 +327,8 @@ namespace osu.Game.Skinning private string scorePrefix => GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; + private string comboPrefix => GetConfig(LegacySkinConfiguration.LegacySetting.ComboPrefix)?.Value ?? "score"; + private bool hasScoreFont => this.HasFont(scorePrefix); public override Drawable GetDrawableComponent(ISkinComponent component) @@ -349,6 +351,12 @@ namespace osu.Game.Skinning case HUDSkinComponents.AccuracyCounter: return new LegacyAccuracyCounter(this); + case HUDSkinComponents.ComboText: + return new LegacySpriteText(this, comboPrefix) + { + Spacing = new Vector2(-(GetConfig(LegacySkinConfiguration.LegacySetting.ComboOverlap)?.Value ?? -2), 0) + }; + case HUDSkinComponents.ScoreText: return new LegacySpriteText(this, scorePrefix) { From 8a3bce3cc3efe0fbfd07bda9ad9fb3ec6c6b528c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 18:19:09 +0900 Subject: [PATCH 280/350] Fix osu!catch showing two combo counters for legacy skins --- .../Skinning/CatchLegacySkinTransformer.cs | 19 ++++++++++++++++--- osu.Game/Screens/Play/Player.cs | 6 +++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 916b4c5192..22db147e32 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -13,6 +13,11 @@ namespace osu.Game.Rulesets.Catch.Skinning { public class CatchLegacySkinTransformer : LegacySkinTransformer { + /// + /// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. + /// + private bool providesComboCounter => this.HasFont(GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"); + public CatchLegacySkinTransformer(ISkinSource source) : base(source) { @@ -20,6 +25,16 @@ namespace osu.Game.Rulesets.Catch.Skinning public override Drawable GetDrawableComponent(ISkinComponent component) { + if (component is HUDSkinComponent hudComponent) + { + switch (hudComponent.Component) + { + case HUDSkinComponents.ComboCounter: + // catch may provide its own combo counter; hide the default. + return providesComboCounter ? Drawable.Empty() : null; + } + } + if (!(component is CatchSkinComponent catchSkinComponent)) return null; @@ -55,10 +70,8 @@ namespace osu.Game.Rulesets.Catch.Skinning this.GetAnimation("fruit-ryuuta", true, true, true); case CatchSkinComponents.CatchComboCounter: - var comboFont = GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; - // For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. - if (this.HasFont(comboFont)) + if (providesComboCounter) return new LegacyCatchComboCounter(Source); break; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 56b212291a..df0a52a0e8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -221,8 +221,12 @@ namespace osu.Game.Screens.Play createGameplayComponents(Beatmap.Value, playableBeatmap) }); + // also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) + // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. + var hudRulesetContainer = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); + // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. - GameplayClockContainer.Add(createOverlayComponents(Beatmap.Value)); + GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value))); if (!DrawableRuleset.AllowGameplayOverlays) { From 0437f7e7e982fef41611ab428c949744432a4a11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 18:22:18 +0900 Subject: [PATCH 281/350] Delete outdated test scene Has been replaced by the four new skinnable tests for each component. --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs deleted file mode 100644 index 34c657bf7f..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play.HUD; -using osuTK; - -namespace osu.Game.Tests.Visual.Gameplay -{ - [TestFixture] - public class TestSceneScoreCounter : OsuTestScene - { - public TestSceneScoreCounter() - { - int numerator = 0, denominator = 0; - - ScoreCounter score = new DefaultScoreCounter - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Margin = new MarginPadding(20), - }; - Add(score); - - LegacyComboCounter comboCounter = new LegacyComboCounter - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Margin = new MarginPadding(10), - }; - Add(comboCounter); - - PercentageCounter accuracyCounter = new PercentageCounter - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Position = new Vector2(-20, 60), - }; - Add(accuracyCounter); - - AddStep(@"Reset all", delegate - { - score.Current.Value = 0; - comboCounter.Current.Value = 0; - numerator = denominator = 0; - accuracyCounter.SetFraction(0, 0); - }); - - AddStep(@"Hit! :D", delegate - { - score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current.Value > 0 ? comboCounter.Current.Value - 1 : 0) / 25.0); - comboCounter.Current.Value++; - numerator++; - denominator++; - accuracyCounter.SetFraction(numerator, denominator); - }); - - AddStep(@"miss...", delegate - { - comboCounter.Current.Value = 0; - denominator++; - accuracyCounter.SetFraction(numerator, denominator); - }); - } - } -} From cc1128314354b6393d5622f27dd032ac58e56968 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Fri, 16 Oct 2020 11:27:02 +0200 Subject: [PATCH 282/350] Use string.Starts-/EndsWith char overloads --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Screens/Select/FilterQueryParser.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index b947056ebd..8bdc804311 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -593,7 +593,7 @@ namespace osu.Game.Database var fileInfos = new List(); string prefix = reader.Filenames.GetCommonPrefix(); - if (!(prefix.EndsWith("/", StringComparison.Ordinal) || prefix.EndsWith("\\", StringComparison.Ordinal))) + if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) prefix = string.Empty; // import files to manager diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 56cced9c04..a0ddab702e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -181,7 +181,7 @@ namespace osu.Game if (args?.Length > 0) { - var paths = args.Where(a => !a.StartsWith(@"-", StringComparison.Ordinal)).ToArray(); + var paths = args.Where(a => !a.StartsWith('-')).ToArray(); if (paths.Length > 0) Task.Run(() => Import(paths)); } @@ -289,7 +289,7 @@ namespace osu.Game public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { - if (url.StartsWith("/", StringComparison.Ordinal)) + if (url.StartsWith('/')) url = $"{API.Endpoint}{url}"; externalLinkOpener.OpenUrlExternally(url); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index fa2beb2652..4b6b3be45c 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -80,9 +80,9 @@ namespace osu.Game.Screens.Select private static int getLengthScale(string value) => value.EndsWith("ms", StringComparison.Ordinal) ? 1 : - value.EndsWith("s", StringComparison.Ordinal) ? 1000 : - value.EndsWith("m", StringComparison.Ordinal) ? 60000 : - value.EndsWith("h", StringComparison.Ordinal) ? 3600000 : 1000; + value.EndsWith('s') ? 1000 : + value.EndsWith('m') ? 60000 : + value.EndsWith('h') ? 3600000 : 1000; private static bool parseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); From cbaad4eb56bf69a733b15c46f0332ec8b78f82cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 18:34:14 +0900 Subject: [PATCH 283/350] Adjust accuracy display to match stable --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 9354b2b3bc..0d3adeb3ea 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning Anchor = Anchor.TopRight; Origin = Anchor.TopRight; - Scale = new Vector2(0.75f); + Scale = new Vector2(0.6f); Margin = new MarginPadding(10); this.skin = skin; From 2ba8bc45fd1f68b54df68435eb0d88c3d28fff1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 18:37:24 +0900 Subject: [PATCH 284/350] Also add slight adjustment to score display --- osu.Game/Skinning/LegacyScoreCounter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index f94bef6652..93b50e0ac1 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Skinning { @@ -28,6 +29,7 @@ namespace osu.Game.Skinning // base class uses int for display, but externally we bind to ScoreProcesssor as a double for now. Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); + Scale = new Vector2(0.96f); Margin = new MarginPadding(10); } From fe3a23750c6bbda7427e765aa93007b1ee13a6b7 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Fri, 16 Oct 2020 11:52:29 +0200 Subject: [PATCH 285/350] Use char overloads for string methods --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 2 +- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- osu.Game/Skinning/GameplaySkinComponent.cs | 2 +- osu.Game/Skinning/HUDSkinComponent.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index acab525821..8d1f0e59bf 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps [JsonIgnore] public string StoredBookmarks { - get => string.Join(",", Bookmarks); + get => string.Join(',', Bookmarks); set { if (string.IsNullOrEmpty(value)) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index f7ed57f207..16f46581c5 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -196,7 +196,7 @@ namespace osu.Game.Online.Chat if (target == null) return; - var parameters = text.Split(new[] { ' ' }, 2); + var parameters = text.Split(' ', 2); string command = parameters[0]; string content = parameters.Length == 2 ? parameters[1] : string.Empty; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 648e4a762b..d2a117876d 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -111,7 +111,7 @@ namespace osu.Game.Online.Chat public static LinkDetails GetLinkDetails(string url) { - var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + var args = url.Split('/', StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); switch (args[0]) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 946831d13b..ebee377a51 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Profile.Header if (string.IsNullOrEmpty(content)) return false; // newlines could be contained in API returned user content. - content = content.Replace("\n", " "); + content = content.Replace('\n', ' '); bottomLinkContainer.AddIcon(icon, text => { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 7dcbc52cea..44b22033dc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { string[] ss = split[5].Split(':'); endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); - readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo); } result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime); diff --git a/osu.Game/Skinning/GameplaySkinComponent.cs b/osu.Game/Skinning/GameplaySkinComponent.cs index 2aa380fa90..80f6efc07a 100644 --- a/osu.Game/Skinning/GameplaySkinComponent.cs +++ b/osu.Game/Skinning/GameplaySkinComponent.cs @@ -18,6 +18,6 @@ namespace osu.Game.Skinning protected virtual string ComponentName => Component.ToString(); public string LookupName => - string.Join("/", new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); + string.Join('/', new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); } } diff --git a/osu.Game/Skinning/HUDSkinComponent.cs b/osu.Game/Skinning/HUDSkinComponent.cs index 041beb68f2..cc053421b7 100644 --- a/osu.Game/Skinning/HUDSkinComponent.cs +++ b/osu.Game/Skinning/HUDSkinComponent.cs @@ -17,6 +17,6 @@ namespace osu.Game.Skinning protected virtual string ComponentName => Component.ToString(); public string LookupName => - string.Join("/", new[] { "HUD", ComponentName }.Where(s => !string.IsNullOrEmpty(s))); + string.Join('/', new[] { "HUD", ComponentName }.Where(s => !string.IsNullOrEmpty(s))); } } From 2586990301e0da95f5ffd272f942b86859bb595a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Oct 2020 23:19:34 +0900 Subject: [PATCH 286/350] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3df894fbcc..1d2cf22b28 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8b10f0a7f7..133855c6c4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 88abbca73d..73faa8541e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 81cc5e1c42e787f906fda4c6c881474c74f33aac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Oct 2020 23:31:01 +0900 Subject: [PATCH 287/350] Silence EF warning due to ordinal being unsupported --- osu.Game/Rulesets/RulesetStore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index c12d418771..c4639375da 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -100,7 +100,8 @@ namespace osu.Game.Rulesets { // todo: StartsWith can be changed to Equals on 2020-11-08 // This is to give users enough time to have their database use new abbreviated info). - if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) + // ReSharper disable once StringStartsWithIsCultureSpecific (silences EF warning of ordinal being unsupported) + if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo)) == null) context.RulesetInfo.Add(r.RulesetInfo); } From 6385d5f3692b95bbdcea9819292ce221e2795999 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Oct 2020 23:40:44 +0900 Subject: [PATCH 288/350] Replace with local tolist --- osu.Game/Rulesets/RulesetStore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index c4639375da..d422bca087 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -96,12 +96,13 @@ namespace osu.Game.Rulesets context.SaveChanges(); // add any other modes + var existingRulesets = context.RulesetInfo.ToList(); + foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) { // todo: StartsWith can be changed to Equals on 2020-11-08 // This is to give users enough time to have their database use new abbreviated info). - // ReSharper disable once StringStartsWithIsCultureSpecific (silences EF warning of ordinal being unsupported) - if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo)) == null) + if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) context.RulesetInfo.Add(r.RulesetInfo); } From bba9a0b2fe5be16ae37338cddb307121de41aaf1 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 17 Oct 2020 00:25:16 +0800 Subject: [PATCH 289/350] set sprite text anchor and origin to top right --- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index e54c4e8eb4..fc7863fc4e 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -33,6 +33,6 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); + protected sealed override OsuSpriteText CreateSpriteText() => ((OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText))).With(s => s.Anchor = s.Origin = Anchor.TopRight); } } From b60dfc55b6625bea0f803c2ddd9d3c389d9ca7cb Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 16 Oct 2020 19:21:51 +0200 Subject: [PATCH 290/350] Apply review suggestions. --- osu.Android/GameplayScreenRotationLocker.cs | 6 +++--- osu.Android/OsuGameActivity.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index d1f4caba52..07cca8c2f1 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -17,14 +17,14 @@ namespace osu.Android private void load(OsuGame game) { localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); - localUserPlaying.BindValueChanged(_ => updateLock()); + localUserPlaying.BindValueChanged(userPlaying => updateLock(userPlaying)); } - private void updateLock() + private void updateLock(ValueChangedEvent userPlaying) { OsuGameActivity.Activity.RunOnUiThread(() => { - OsuGameActivity.Activity.RequestedOrientation = localUserPlaying.Value ? ScreenOrientation.Locked : ScreenOrientation.FullUser; + OsuGameActivity.Activity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index c2b28f3de4..d4d2b83502 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -12,7 +12,7 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { - internal static Activity Activity; + internal static Activity Activity { get; private set; } protected override Framework.Game CreateGame() => new OsuGameAndroid(); From e4463254d7feab088262dfb84826f4ce5a04ba43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 15:29:30 +0200 Subject: [PATCH 291/350] Add test coverage for score counter alignment --- .../Visual/Gameplay/TestSceneSkinnableScoreCounter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs index 2d5003d1da..fc63340f20 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -43,5 +44,11 @@ namespace osu.Game.Tests.Visual.Gameplay s.Current.Value += 300; }); } + + [Test] + public void TestVeryLargeScore() + { + AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_00_000_000)); + } } } From 0acc86f75724e6d1e5f348ee7878b7249be6078c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 15:31:35 +0200 Subject: [PATCH 292/350] Split line for readability --- osu.Game/Skinning/LegacyScoreCounter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index fc7863fc4e..5bffeff5a8 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -33,6 +33,8 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => ((OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText))).With(s => s.Anchor = s.Origin = Anchor.TopRight); + protected sealed override OsuSpriteText CreateSpriteText() + => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) + .With(s => s.Anchor = s.Origin = Anchor.TopRight); } } From a5b0307cfb472342bb56a08548b8245d7a8604be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 15:36:21 +0200 Subject: [PATCH 293/350] Apply same fix to legacy accuracy counter --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 29d7046694..5eda374337 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -29,7 +29,9 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => (OsuSpriteText)skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); + protected sealed override OsuSpriteText CreateSpriteText() + => (OsuSpriteText)skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) + ?.With(s => s.Anchor = s.Origin = Anchor.TopRight); protected override void Update() { From 8aeeed9402e2de7d6de6e477adc69bf914ed6f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 15:47:37 +0200 Subject: [PATCH 294/350] Fix weird number formatting in test --- .../Visual/Gameplay/TestSceneSkinnableScoreCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs index fc63340f20..e212ceeba7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestVeryLargeScore() { - AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_00_000_000)); + AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_000_000_000)); } } } From 5b96f0156413e2694853b1a67557189fd4c7fb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 14:53:29 +0200 Subject: [PATCH 295/350] Fix key counter actions displaying out of order --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index f2ac61eaf4..07de2bf601 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -136,7 +136,11 @@ namespace osu.Game.Rulesets.UI KeyBindingContainer.Add(receptor); keyCounter.SetReceptor(receptor); - keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); + keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings + .Select(b => b.GetAction()) + .Distinct() + .OrderBy(action => action) + .Select(action => new KeyCounterAction(action))); } public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler From 9cd595800a5b826f8fac7a62c8eabfd7157a32f2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 18 Oct 2020 19:42:05 +0200 Subject: [PATCH 296/350] Subscribe to event handler instead. --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index 07cca8c2f1..d25e22a0c2 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -17,7 +17,7 @@ namespace osu.Android private void load(OsuGame game) { localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); - localUserPlaying.BindValueChanged(userPlaying => updateLock(userPlaying)); + localUserPlaying.ValueChanged += updateLock; } private void updateLock(ValueChangedEvent userPlaying) From 371aecfca0856499cd0b014b4afab71f0bcd4b9f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 18 Oct 2020 20:07:42 +0200 Subject: [PATCH 297/350] Fetch OsuGameActivity through DI instead. --- osu.Android/GameplayScreenRotationLocker.cs | 7 +++++-- osu.Android/OsuGameActivity.cs | 6 +----- osu.Android/OsuGameAndroid.cs | 10 ++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index d25e22a0c2..fb471ceb78 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -13,6 +13,9 @@ namespace osu.Android { private Bindable localUserPlaying; + [Resolved] + private OsuGameActivity gameActivity { get; set; } + [BackgroundDependencyLoader] private void load(OsuGame game) { @@ -22,9 +25,9 @@ namespace osu.Android private void updateLock(ValueChangedEvent userPlaying) { - OsuGameActivity.Activity.RunOnUiThread(() => + gameActivity.RunOnUiThread(() => { - OsuGameActivity.Activity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser; + gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index d4d2b83502..7e250dce0e 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -12,14 +12,10 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { - internal static Activity Activity { get; private set; } - - protected override Framework.Game CreateGame() => new OsuGameAndroid(); + protected override Framework.Game CreateGame() => new OsuGameAndroid(this); protected override void OnCreate(Bundle savedInstanceState) { - Activity = this; - // The default current directory on android is '/'. // On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage. // In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory. diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 887a8395e3..21d6336b2c 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -4,6 +4,7 @@ using System; using Android.App; using Android.OS; +using osu.Framework.Allocation; using osu.Game; using osu.Game.Updater; @@ -11,6 +12,15 @@ namespace osu.Android { public class OsuGameAndroid : OsuGame { + [Cached] + private readonly OsuGameActivity gameActivity; + + public OsuGameAndroid(OsuGameActivity activity) + : base(null) + { + gameActivity = activity; + } + public override Version AssemblyVersion { get From 4590d9b93b874550fde5a921bedff9353d48b69a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 13:04:12 +0900 Subject: [PATCH 298/350] Remove outdated comment logic --- osu.Game/Screens/Select/BeatmapCarousel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c011ea7e05..83e20909a1 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -762,7 +762,6 @@ namespace osu.Game.Screens.Select // scroll position at currentY makes the set panel appear at the very top of the carousel's screen space // move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas) // then reapply the top semi-transparent area (because carousel's screen space starts below it) - // and finally add half of the panel's own height to achieve vertical centering of the panel itself scrollTarget = currentY + DrawableCarouselBeatmapSet.HEIGHT - visibleHalfHeight + BleedTop; foreach (var b in set.Beatmaps) From ee0efa0b4c1a5e19b665e3bf89999714d24408c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 13:05:42 +0900 Subject: [PATCH 299/350] Fix off-by-one in display range retrieval logic --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 83e20909a1..04eff79533 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -590,7 +590,7 @@ namespace osu.Game.Screens.Select { displayedRange = newDisplayRange; - var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first); + var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1); foreach (var panel in ScrollableContent.Children) { From bff3856c83ec9459185734fffeeef1aee3a41a29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 13:13:32 +0900 Subject: [PATCH 300/350] Account for panel height when removing as off-screen --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 04eff79533..06327bac3f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -602,7 +602,7 @@ namespace osu.Game.Screens.Select // panel loaded as drawable but not required by visible range. // remove but only if too far off-screen - if (panel.Y < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) + if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) { // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). panel.ClearTransforms(); From cb1784a846901a15673575d25d2dcfc92ce85515 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:05:28 +0900 Subject: [PATCH 301/350] Fix score displays using non-matching zero padding depending on user score display mode --- .../Graphics/UserInterface/RollingCounter.cs | 12 +++++-- .../Graphics/UserInterface/ScoreCounter.cs | 14 ++++---- osu.Game/Screens/Play/HUD/IScoreCounter.cs | 6 ++++ .../Screens/Play/HUD/SkinnableScoreCounter.cs | 32 +++++++++++++++++++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 91a557094d..b96181416d 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -56,8 +56,7 @@ namespace osu.Game.Graphics.UserInterface return; displayedCount = value; - if (displayedCountSpriteText != null) - displayedCountSpriteText.Text = FormatCount(value); + UpdateDisplay(); } } @@ -73,10 +72,17 @@ namespace osu.Game.Graphics.UserInterface private void load() { displayedCountSpriteText = CreateSpriteText(); - displayedCountSpriteText.Text = FormatCount(DisplayedCount); + + UpdateDisplay(); Child = displayedCountSpriteText; } + protected void UpdateDisplay() + { + if (displayedCountSpriteText != null) + displayedCountSpriteText.Text = FormatCount(DisplayedCount); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 17e5ceedb9..d75e49a4ce 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -17,20 +18,19 @@ namespace osu.Game.Graphics.UserInterface /// public bool UseCommaSeparator { get; } - /// - /// How many leading zeroes the counter has. - /// - public uint LeadingZeroes { get; } + public Bindable RequiredDisplayDigits { get; } = new Bindable(); /// /// Displays score. /// /// How many leading zeroes the counter will have. /// Whether comma separators should be displayed. - protected ScoreCounter(uint leading = 0, bool useCommaSeparator = false) + protected ScoreCounter(int leading = 0, bool useCommaSeparator = false) { UseCommaSeparator = useCommaSeparator; - LeadingZeroes = leading; + + RequiredDisplayDigits.Value = leading; + RequiredDisplayDigits.BindValueChanged(_ => UpdateDisplay()); } protected override double GetProportionalDuration(double currentValue, double newValue) @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface protected override string FormatCount(double count) { - string format = new string('0', (int)LeadingZeroes); + string format = new string('0', RequiredDisplayDigits.Value); if (UseCommaSeparator) { diff --git a/osu.Game/Screens/Play/HUD/IScoreCounter.cs b/osu.Game/Screens/Play/HUD/IScoreCounter.cs index 2d39a64cfe..7f5e81d5ef 100644 --- a/osu.Game/Screens/Play/HUD/IScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/IScoreCounter.cs @@ -15,5 +15,11 @@ namespace osu.Game.Screens.Play.HUD /// The current score to be displayed. /// Bindable Current { get; } + + /// + /// The number of digits required to display most sane scores. + /// This may be exceeded in very rare cases, but is useful to pad or space the display to avoid it jumping around. + /// + Bindable RequiredDisplayDigits { get; } } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs index a442ad0d9a..b46f5684b1 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs @@ -1,7 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD @@ -10,12 +14,38 @@ namespace osu.Game.Screens.Play.HUD { public Bindable Current { get; } = new Bindable(); + private Bindable scoreDisplayMode; + + public Bindable RequiredDisplayDigits { get; } = new Bindable(); + public SkinnableScoreCounter() : base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter()) { CentreComponent = false; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + scoreDisplayMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + scoreDisplayMode.BindValueChanged(scoreMode => + { + switch (scoreMode.NewValue) + { + case ScoringMode.Standardised: + RequiredDisplayDigits.Value = 6; + break; + + case ScoringMode.Classic: + RequiredDisplayDigits.Value = 8; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(scoreMode)); + } + }, true); + } + private IScoreCounter skinnedCounter; protected override void SkinChanged(ISkinSource skin, bool allowFallback) @@ -23,7 +53,9 @@ namespace osu.Game.Screens.Play.HUD base.SkinChanged(skin, allowFallback); skinnedCounter = Drawable as IScoreCounter; + skinnedCounter?.Current.BindTo(Current); + skinnedCounter?.RequiredDisplayDigits.BindTo(RequiredDisplayDigits); } } } From e3b47083fc85ebc324575645b8d4c33c5661253f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:05:41 +0900 Subject: [PATCH 302/350] Add "scoring" as keyword to more easily find score display mode setting --- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 73968761e2..66b3b8c4ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -76,7 +76,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsEnumDropdown { LabelText = "Score display mode", - Current = config.GetBindable(OsuSetting.ScoreDisplayMode) + Current = config.GetBindable(OsuSetting.ScoreDisplayMode), + Keywords = new[] { "scoring" } } }; From cdb649476b8bf327c9ca561a26ce9ffabd660e32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:33:53 +0900 Subject: [PATCH 303/350] Allow legacy text to display fixed width correctly --- osu.Game/Skinning/LegacySpriteText.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 8394657b1c..d7a3975c72 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; using osu.Framework.Text; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Skinning { @@ -17,11 +18,12 @@ namespace osu.Game.Skinning Shadow = false; UseFullGlyphHeight = false; - Font = new FontUsage(font, 1); + Font = new FontUsage(font, 1, fixedWidth: true); glyphStore = new LegacyGlyphStore(skin); } - protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); + protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => + new TextBuilder(glyphStore, Font, MaxWidth, UseFullGlyphHeight, Vector2.Zero, Spacing, CharactersBacking, neverFixedWidthCharacters: new[] { ',', '.', '%', 'x' }, fixedWidthCalculationCharacter: '5'); private class LegacyGlyphStore : ITexturedGlyphLookupStore { From ba99c5c134d5563725c46b57b6854befa2c91ac8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:39:02 +0900 Subject: [PATCH 304/350] Remove rolling delay on default combo counter --- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index a5c33f6dbe..63e7a88550 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -15,8 +15,6 @@ namespace osu.Game.Screens.Play.HUD { private readonly Vector2 offset = new Vector2(20, 5); - protected override double RollingDuration => 750; - [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } From 39cf27637e157ec13d8378ba89999d8a64809fd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:59:03 +0900 Subject: [PATCH 305/350] Update to use virtual methods instead of reconstructing TextBuilder --- osu.Game/Skinning/LegacySpriteText.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index d7a3975c72..5d0e312f7c 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; using osu.Framework.Text; using osu.Game.Graphics.Sprites; -using osuTK; namespace osu.Game.Skinning { @@ -13,6 +12,10 @@ namespace osu.Game.Skinning { private readonly LegacyGlyphStore glyphStore; + protected override char FixedWidthReferenceCharacter => '5'; + + protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' }; + public LegacySpriteText(ISkin skin, string font = "score") { Shadow = false; @@ -22,8 +25,7 @@ namespace osu.Game.Skinning glyphStore = new LegacyGlyphStore(skin); } - protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => - new TextBuilder(glyphStore, Font, MaxWidth, UseFullGlyphHeight, Vector2.Zero, Spacing, CharactersBacking, neverFixedWidthCharacters: new[] { ',', '.', '%', 'x' }, fixedWidthCalculationCharacter: '5'); + protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); private class LegacyGlyphStore : ITexturedGlyphLookupStore { From 7ed862edd7f81ff83a8b1dc98bed01a402977af6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:08:49 +0900 Subject: [PATCH 306/350] Add comment about migration code --- osu.Game.Tournament/IO/TournamentStorage.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 6505135b42..49906eff6d 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -36,6 +36,10 @@ namespace osu.Game.Tournament.IO public override void Migrate(Storage newStorage) { + // this migration only happens once on moving to the per-tournament storage system. + // listed files are those known at that point in time. + // this can be removed at some point in the future (6 months obsoletion would mean 2021-04-19) + var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(newStorage.GetFullPath(".")); @@ -50,6 +54,7 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", destination); + ChangeTargetStorage(newStorage); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); From 9c566e7ffbe2c289306a17d494f82802d8fc4ec7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:34:46 +0900 Subject: [PATCH 307/350] Update tests to use correct parameters of CleanRunGameHost --- .../NonVisual/CustomTourneyDirectoryTest.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index b75a9a6929..567d9f0d62 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -20,14 +20,14 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestDefaultDirectory() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - var defaultStorage = Path.Combine(tournamentBasePath(nameof(TestDefaultDirectory)), "default"); - Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorage)); + + Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default"))); } finally { @@ -39,7 +39,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestCustomDirectory() { - using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) + using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file. { string osuDesktopStorage = basePath(nameof(TestCustomDirectory)); const string custom_tournament = "custom"; @@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Tests.NonVisual storage = osu.Dependencies.Get(); - Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), custom_tournament))); + Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", custom_tournament))); } finally { @@ -70,7 +70,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestMigration() { - using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) + using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration. { string osuRoot = basePath(nameof(TestMigration)); string configFile = Path.Combine(osuRoot, "tournament.ini"); @@ -115,7 +115,7 @@ namespace osu.Game.Tournament.Tests.NonVisual var storage = osu.Dependencies.Get(); - var migratedPath = Path.Combine(tournamentBasePath(nameof(TestMigration)), "default"); + string migratedPath = Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default"); videosPath = Path.Combine(migratedPath, "videos"); modsPath = Path.Combine(migratedPath, "mods"); @@ -165,7 +165,5 @@ namespace osu.Game.Tournament.Tests.NonVisual } private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance); - - private string tournamentBasePath(string testInstance) => Path.Combine(basePath(testInstance), "tournaments"); } } From 31f6051db9504e4cef04fcc7fdd819a7f0827343 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:36:27 +0900 Subject: [PATCH 308/350] Add missing xmldoc --- osu.Game/IO/MigratableStorage.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 21087d7dc6..1b76725b04 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,7 +14,14 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { + /// + /// A relative list of directory paths which should not be migrated. + /// public virtual string[] IgnoreDirectories => Array.Empty(); + + /// + /// A relative list of file paths which should not be migrated. + /// public virtual string[] IgnoreFiles => Array.Empty(); protected MigratableStorage(Storage storage, string subPath = null) From 3f41003d355c82b28fffd11f27394283f2e67847 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:48:15 +0900 Subject: [PATCH 309/350] Move video store out of TournamentStorage There was no reason it should be nested inside. --- osu.Game.Tournament/Components/TourneyVideo.cs | 5 ++--- osu.Game.Tournament/IO/TournamentStorage.cs | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 794b72b3a9..2709580385 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; -using osu.Framework.Platform; using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Tournament.IO; @@ -28,9 +27,9 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(Storage storage) + private void load(TournamentVideoResourceStore storage) { - var stream = (storage as TournamentStorage)?.VideoStore.GetStream(filename); + var stream = storage.GetStream(filename); if (stream != null) { diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 49906eff6d..2e8a6ce667 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -14,7 +14,6 @@ namespace osu.Game.Tournament.IO private const string default_tournament = "default"; private readonly Storage storage; private readonly TournamentStorageManager storageConfig; - public TournamentVideoResourceStore VideoStore { get; } public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) @@ -30,7 +29,6 @@ namespace osu.Game.Tournament.IO else Migrate(UnderlyingStorage.GetStorageForDirectory(default_tournament)); - VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } From daceb0c04991d0b103ed81434cd05db4decd64f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:48:33 +0900 Subject: [PATCH 310/350] Fix texture store not being initialised correctly Without this change flags/mods would not work as expected. The video store was being added as the texture store incorrectly. --- osu.Game.Tournament/TournamentGameBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 6a533f96d8..dbda6aa023 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -40,8 +40,9 @@ namespace osu.Game.Tournament Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); + dependencies.Cache(new TournamentVideoResourceStore(storage)); - Textures.AddStore(new TextureLoaderStore(storage.VideoStore)); + Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage))); readBracket(); From f597572d739eeb71ac1a086cbf353e7233d236e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 16:02:39 +0900 Subject: [PATCH 311/350] Add comment with reasoning for TopRight anchor --- osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index af0043436a..ec68223a3d 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -73,6 +73,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, new Container { + // top right works better when the vertical height of the component changes smoothly (avoids weird layout animations). Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, From 401dd2db37a55460a8f1332daa3d174e59f14a41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 16:55:00 +0900 Subject: [PATCH 312/350] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1d2cf22b28..2d531cf01e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 133855c6c4..de7bde824f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 73faa8541e..9c22dec330 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 79a17b23715b9fe0982d11975494de0cb43413da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 16:57:08 +0900 Subject: [PATCH 313/350] Reapply waveform colour fix --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index be3bca3242..9aff4ddf8f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform = new WaveformGraph { RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), + BaseColour = colours.Blue.Opacity(0.2f), LowColour = colours.BlueLighter, MidColour = colours.BlueDark, HighColour = colours.BlueDarker, From cd7c3021caf54210b5f2e3d80fb54d8d9a2917f8 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 19 Oct 2020 10:01:24 +0200 Subject: [PATCH 314/350] Trigger lock update on loading. --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index fb471ceb78..25bd659a5d 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -20,7 +20,7 @@ namespace osu.Android private void load(OsuGame game) { localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); - localUserPlaying.ValueChanged += updateLock; + localUserPlaying.BindValueChanged(updateLock, true); } private void updateLock(ValueChangedEvent userPlaying) From 6d22f0e1962dd0530b473aca1105d6a6b01258c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 17:15:13 +0900 Subject: [PATCH 315/350] Use existing copy method and update xmldoc --- osu.Game/Online/Multiplayer/Room.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 5feebe8da1..9a21543b2e 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -104,21 +104,17 @@ namespace osu.Game.Online.Multiplayer public readonly Bindable Position = new Bindable(-1); /// - /// Create a copy of this room, without information specific to it, such as Room ID or host + /// Create a copy of this room without online information. + /// Should be used to create a local copy of a room for submitting in the future. /// public Room CreateCopy() { - Room newRoom = new Room - { - Name = { Value = Name.Value }, - Availability = { Value = Availability.Value }, - Type = { Value = Type.Value }, - MaxParticipants = { Value = MaxParticipants.Value } - }; + var copy = new Room(); - newRoom.Playlist.AddRange(Playlist); + copy.CopyFrom(this); + copy.RoomID.Value = null; - return newRoom; + return copy; } public void CopyFrom(Room other) From 437ca91b9441dc5891b39f81301b2c2af0989bf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 17:15:35 +0900 Subject: [PATCH 316/350] Use DI to open the copy rather than passing in an ugly action --- .../Screens/Multi/Lounge/Components/DrawableRoom.cs | 10 +++++++--- .../Screens/Multi/Lounge/Components/RoomsContainer.cs | 8 -------- osu.Game/Screens/Multi/Multiplayer.cs | 11 ++++++----- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index db75df6054..01a85382e4 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -38,11 +38,12 @@ namespace osu.Game.Screens.Multi.Lounge.Components public event Action StateChanged; - public Action DuplicateRoom; - private readonly Box selectionBox; private CachedModelDependencyContainer dependencies; + [Resolved(canBeNull: true)] + private Multiplayer multiplayer { get; set; } + [Resolved] private BeatmapManager beatmaps { get; set; } @@ -239,7 +240,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Create copy", MenuItemType.Standard, DuplicateRoom) + new OsuMenuItem("Create copy", MenuItemType.Standard, () => + { + multiplayer?.CreateRoom(Room.CreateCopy()); + }) }; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index d9af38d19e..60c6aa1d8a 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -111,14 +111,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components { roomFlow.Add(new DrawableRoom(room) { - DuplicateRoom = () => - { - Room newRoom = room.CreateCopy(); - if (!newRoom.Name.Value.StartsWith("Copy of ")) - newRoom.Name.Value = $"Copy of {room.Name.Value}"; - - loungeSubScreen?.Open(newRoom); - }, Action = () => { if (room == selectedRoom.Value) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index cdaeebefb7..27f774e9ec 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -134,7 +134,7 @@ namespace osu.Game.Screens.Multi { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Action = createRoom + Action = () => CreateRoom() }, roomManager = new RoomManager() } @@ -289,10 +289,11 @@ namespace osu.Game.Screens.Multi logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut(); } - private void createRoom() - { - loungeSubScreen.Open(new Room { Name = { Value = $"{api.LocalUser}'s awesome room" } }); - } + /// + /// Create a new room. + /// + /// An optional template to use when creating the room. + public void CreateRoom(Room room = null) => loungeSubScreen.Open(room ?? new Room { Name = { Value = $"{api.LocalUser}'s awesome room" } }); private void beginHandlingTrack() { From 4024b44a53b0642bb15a18871caa9ecd80342925 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 17:41:21 +0900 Subject: [PATCH 317/350] Fix unsafe manipulation of parent's children from child --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 9289a6162c..a221ca7966 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -145,11 +145,19 @@ namespace osu.Game.Rulesets.Catch.UI } }; - trailsTarget.Add(trails = new CatcherTrailDisplay(this)); + trails = new CatcherTrailDisplay(this); updateCatcher(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // don't add in above load as we may potentially modify a parent in an unsafe manner. + trailsTarget.Add(trails); + } + /// /// Creates proxied content to be displayed beneath hitobjects. /// From 044622a7a60f3e588b2d2b6543ab6e81547bedc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 18:41:17 +0900 Subject: [PATCH 318/350] Fix out of bounds issues --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 06327bac3f..54860a894b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -662,7 +662,7 @@ namespace osu.Game.Screens.Select // as we can't be 100% sure on the size of individual carousel drawables, // always play it safe and extend bounds by one. firstIndex = Math.Max(0, firstIndex - 1); - lastIndex = Math.Min(visibleItems.Count, lastIndex + 1); + lastIndex = Math.Clamp(lastIndex + 1, firstIndex, visibleItems.Count - 1); return (firstIndex, lastIndex); } From 1c2185e9691c1a595145d123555ad7decd5e2ffb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 18:41:28 +0900 Subject: [PATCH 319/350] Replace comment with link to issue --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 54860a894b..e3cd3461c2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -895,7 +895,7 @@ namespace osu.Game.Screens.Select /// public bool UserScrolling { get; private set; } - // ReSharper disable once OptionalParameterHierarchyMismatch fuck off rider + // ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910) protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { UserScrolling = true; From 28eae5d26b0a1e8b69cfa4dbf7ea6b5f03f62b9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 19:03:22 +0900 Subject: [PATCH 320/350] Fix migration test failures due to finalizer disposal of LocalConfigManager --- osu.Game/OsuGameBase.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 84766f196a..2d609668af 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -371,8 +371,10 @@ namespace osu.Game protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + RulesetStore?.Dispose(); BeatmapManager?.Dispose(); + LocalConfig?.Dispose(); contextFactory.FlushConnections(); } From 9106e97c37697812b3af9f508724273c27d4f457 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 19:10:01 +0900 Subject: [PATCH 321/350] Ensure max value in clamp is at least zero --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e3cd3461c2..92abec0eee 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -662,7 +662,7 @@ namespace osu.Game.Screens.Select // as we can't be 100% sure on the size of individual carousel drawables, // always play it safe and extend bounds by one. firstIndex = Math.Max(0, firstIndex - 1); - lastIndex = Math.Clamp(lastIndex + 1, firstIndex, visibleItems.Count - 1); + lastIndex = Math.Clamp(lastIndex + 1, firstIndex, Math.Max(0, visibleItems.Count - 1)); return (firstIndex, lastIndex); } From d5940193a2dce5dd5e35568b0784c624f1117500 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 19:55:20 +0900 Subject: [PATCH 322/350] Ensure visible items is greater than zero before trying to display a range --- osu.Game/Screens/Select/BeatmapCarousel.cs | 49 ++++++++++++---------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 92abec0eee..83631fd383 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -590,36 +590,39 @@ namespace osu.Game.Screens.Select { displayedRange = newDisplayRange; - var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1); - - foreach (var panel in ScrollableContent.Children) + if (visibleItems.Count > 0) { - if (toDisplay.Remove(panel.Item)) + var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1); + + foreach (var panel in ScrollableContent.Children) { - // panel already displayed. - continue; + if (toDisplay.Remove(panel.Item)) + { + // panel already displayed. + continue; + } + + // panel loaded as drawable but not required by visible range. + // remove but only if too far off-screen + if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) + { + // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). + panel.ClearTransforms(); + panel.Expire(); + } } - // panel loaded as drawable but not required by visible range. - // remove but only if too far off-screen - if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) + // Add those items within the previously found index range that should be displayed. + foreach (var item in toDisplay) { - // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). - panel.ClearTransforms(); - panel.Expire(); + var panel = setPool.Get(p => p.Item = item); + + panel.Depth = item.CarouselYPosition; + panel.Y = item.CarouselYPosition; + + ScrollableContent.Add(panel); } } - - // Add those items within the previously found index range that should be displayed. - foreach (var item in toDisplay) - { - var panel = setPool.Get(p => p.Item = item); - - panel.Depth = item.CarouselYPosition; - panel.Y = item.CarouselYPosition; - - ScrollableContent.Add(panel); - } } // Finally, if the filtered items have changed, animate drawables to their new locations. From a50ca0a1edba6a40af2ad24cc30c1abb86a6a95d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 20:00:17 +0900 Subject: [PATCH 323/350] Fix osu!catch test failures due to trying to retrieve container too early --- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 1e708cce4b..1b8368794c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -123,7 +123,10 @@ namespace osu.Game.Rulesets.Catch.Tests Origin = Anchor.Centre, Scale = new Vector2(4f), }, skin); + }); + AddStep("get trails container", () => + { trails = catcherArea.OfType().Single(); catcherArea.MovableCatcher.SetHyperDashState(2); }); From 053c7a69a64ee67243f6a10d81c92644a824febb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 20:22:48 +0200 Subject: [PATCH 324/350] Fix code style issues & compilation failures --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 ++-- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 5 ++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index a7ccc62e40..1d5d2f9431 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,6 +20,7 @@ using osu.Game.Skinning; using osu.Framework.Allocation; using osu.Game.Configuration; using osu.Framework.Bindables; +using osu.Game.Screens; using osuTK; namespace osu.Game.Rulesets.Osu.UI @@ -36,12 +37,10 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private Bindable showPlayfieldBorder; private readonly IDictionary> poolDictionary = new Dictionary>(); - public OsuPlayfield() { InternalChildren = new Drawable[] @@ -87,6 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI private void load(OsuConfigManager config) { showPlayfieldBorder = config.GetBindable(OsuSetting.ShowPlayfieldBorder); + if (showPlayfieldBorder.Value) { AddInternal(new PlayfieldBorder diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 755f4e990c..e8a07fc831 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -76,14 +76,13 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsEnumDropdown { LabelText = "Score display mode", - Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode), + Current = config.GetBindable(OsuSetting.ScoreDisplayMode), Keywords = new[] { "scoring" } }, new SettingsCheckbox { LabelText = "Show playfield border", - Bindable = config.GetBindable(OsuSetting.ShowPlayfieldBorder) - Current = config.GetBindable(OsuSetting.ScoreDisplayMode), + Current = config.GetBindable(OsuSetting.ShowPlayfieldBorder), }, }; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fc80d4f3df..f4457c6aeb 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] { LayerBelowRuleset, - new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } + new PlayfieldBorder { RelativeSizeAxes = Axes.Both } }), drawableRulesetWrapper, // layers above playfield From 4af3fd1ed68830f250704246e3957bcfb36e92ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 20:41:45 +0200 Subject: [PATCH 325/350] Allow toggling border on & off during gameplay --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 18 ++++++++++-------- osu.Game/Screens/PlayfieldBorder.cs | 7 ++++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 1d5d2f9431..8904345825 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.UI { public class OsuPlayfield : Playfield { + private readonly PlayfieldBorder playfieldBorder; private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; @@ -45,6 +46,11 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { + playfieldBorder = new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both, + Depth = 3 + }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both @@ -86,16 +92,12 @@ namespace osu.Game.Rulesets.Osu.UI private void load(OsuConfigManager config) { showPlayfieldBorder = config.GetBindable(OsuSetting.ShowPlayfieldBorder); - - if (showPlayfieldBorder.Value) - { - AddInternal(new PlayfieldBorder - { - RelativeSizeAxes = Axes.Both - }); - } + showPlayfieldBorder.BindValueChanged(updateBorderVisibility, true); } + private void updateBorderVisibility(ValueChangedEvent settingChange) + => playfieldBorder.State.Value = settingChange.NewValue ? Visibility.Visible : Visibility.Hidden; + public override void Add(DrawableHitObject h) { h.OnNewResult += onNewResult; diff --git a/osu.Game/Screens/PlayfieldBorder.cs b/osu.Game/Screens/PlayfieldBorder.cs index a3be38f0a2..e88b73bc71 100644 --- a/osu.Game/Screens/PlayfieldBorder.cs +++ b/osu.Game/Screens/PlayfieldBorder.cs @@ -11,8 +11,10 @@ namespace osu.Game.Screens /// /// Provides a border around the playfield. /// - public class PlayfieldBorder : CompositeDrawable + public class PlayfieldBorder : VisibilityContainer { + private const int fade_duration = 200; + public PlayfieldBorder() { RelativeSizeAxes = Axes.Both; @@ -28,5 +30,8 @@ namespace osu.Game.Screens AlwaysPresent = true }; } + + protected override void PopIn() => this.FadeIn(fade_duration, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(fade_duration, Easing.OutQuint); } } From 4267d23d598f2635df363b5b42019c91ddc72ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 20:56:34 +0200 Subject: [PATCH 326/350] Move border to more appropriate namespace --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 - osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 - osu.Game/{Screens => Rulesets/UI}/PlayfieldBorder.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) rename osu.Game/{Screens => Rulesets/UI}/PlayfieldBorder.cs (97%) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 8904345825..e536bd1503 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,6 @@ using osu.Game.Skinning; using osu.Framework.Allocation; using osu.Game.Configuration; using osu.Framework.Bindables; -using osu.Game.Screens; using osuTK; namespace osu.Game.Rulesets.Osu.UI diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f4457c6aeb..4c552c5083 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Screens; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Components.TernaryButtons; diff --git a/osu.Game/Screens/PlayfieldBorder.cs b/osu.Game/Rulesets/UI/PlayfieldBorder.cs similarity index 97% rename from osu.Game/Screens/PlayfieldBorder.cs rename to osu.Game/Rulesets/UI/PlayfieldBorder.cs index e88b73bc71..66371c89ad 100644 --- a/osu.Game/Screens/PlayfieldBorder.cs +++ b/osu.Game/Rulesets/UI/PlayfieldBorder.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; -namespace osu.Game.Screens +namespace osu.Game.Rulesets.UI { /// /// Provides a border around the playfield. From dbda18acea2f9644bfc5a618c52ecb08de85a55f Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 19 Oct 2020 12:04:23 -0700 Subject: [PATCH 327/350] Fix autoplay/replay settings going off screen on some legacy skins --- osu.Game/Screens/Play/HUDOverlay.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index c3de249bf8..af5e2b80e1 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -208,17 +208,9 @@ namespace osu.Game.Screens.Play { base.Update(); - float topRightOffset = 0; + // for now align with the accuracy counter. eventually this will be user customisable. + topRightElements.Y = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; - // fetch the bottom-most position of any main ui element that is anchored to the top of the screen. - // consider this kind of temporary. - foreach (var d in mainUIElements) - { - if (d is SkinnableDrawable sd && (sd.Drawable.Anchor & Anchor.y0) > 0) - topRightOffset = Math.Max(sd.Drawable.ScreenSpaceDrawQuad.BottomRight.Y, topRightOffset); - } - - topRightElements.Y = ToLocalSpace(new Vector2(0, topRightOffset)).Y; bottomRightElements.Y = -Progress.Height; } From bca05397350e213213aabce18d4c233187809cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 21:00:49 +0200 Subject: [PATCH 328/350] Move setting to osu! ruleset subsection --- .../Configuration/OsuRulesetConfigManager.cs | 4 +++- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 10 +++++----- osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs | 5 +++++ osu.Game/Configuration/OsuConfigManager.cs | 3 --- .../Settings/Sections/Gameplay/GeneralSettings.cs | 5 ----- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index f76635a932..da8767c017 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Configuration Set(OsuRulesetSetting.SnakingInSliders, true); Set(OsuRulesetSetting.SnakingOutSliders, true); Set(OsuRulesetSetting.ShowCursorTrail, true); + Set(OsuRulesetSetting.ShowPlayfieldBorder, false); } } @@ -26,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Configuration { SnakingInSliders, SnakingOutSliders, - ShowCursorTrail + ShowCursorTrail, + ShowPlayfieldBorder, } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index e536bd1503..26fe686b0d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -18,8 +18,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osu.Framework.Allocation; -using osu.Game.Configuration; using osu.Framework.Bindables; +using osu.Game.Rulesets.Osu.Configuration; using osuTK; namespace osu.Game.Rulesets.Osu.UI @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private Bindable showPlayfieldBorder; + private readonly Bindable showPlayfieldBorder = new BindableBool(); private readonly IDictionary> poolDictionary = new Dictionary>(); @@ -87,10 +87,10 @@ namespace osu.Game.Rulesets.Osu.UI AddRangeInternal(poolDictionary.Values); } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + [BackgroundDependencyLoader(true)] + private void load(OsuRulesetConfigManager config) { - showPlayfieldBorder = config.GetBindable(OsuSetting.ShowPlayfieldBorder); + config?.BindWith(OsuRulesetSetting.ShowPlayfieldBorder, showPlayfieldBorder); showPlayfieldBorder.BindValueChanged(updateBorderVisibility, true); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 3870f303b4..28c609f412 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -39,6 +39,11 @@ namespace osu.Game.Rulesets.Osu.UI LabelText = "Cursor trail", Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) }, + new SettingsCheckbox + { + LabelText = "Show playfield border", + Current = config.GetBindable(OsuRulesetSetting.ShowPlayfieldBorder), + }, }; } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 14be15bb94..78179a781a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -106,8 +106,6 @@ namespace osu.Game.Configuration Set(OsuSetting.IncreaseFirstObjectVisibility, true); Set(OsuSetting.GameplayDisableWinKey, true); - Set(OsuSetting.ShowPlayfieldBorder, false); - // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -241,6 +239,5 @@ namespace osu.Game.Configuration HitLighting, MenuBackgroundSource, GameplayDisableWinKey, - ShowPlayfieldBorder, } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index e8a07fc831..07ad3c6b64 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -79,11 +79,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Current = config.GetBindable(OsuSetting.ScoreDisplayMode), Keywords = new[] { "scoring" } }, - new SettingsCheckbox - { - LabelText = "Show playfield border", - Current = config.GetBindable(OsuSetting.ShowPlayfieldBorder), - }, }; if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) From 7c388f1132cad566f0b8ef05f8326fd1a383899c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 21:20:13 +0200 Subject: [PATCH 329/350] Move editor playfield border locally to osu! composer --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 +++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 912a705d16..d9de9d2c5d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -56,6 +56,11 @@ namespace osu.Game.Rulesets.Osu.Edit [BackgroundDependencyLoader] private void load() { + LayerBelowRuleset.Add(new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } + }); LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4c552c5083..c9dd061b48 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -100,11 +100,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { // layers below playfield - drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] - { - LayerBelowRuleset, - new PlayfieldBorder { RelativeSizeAxes = Axes.Both } - }), + drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(LayerBelowRuleset), drawableRulesetWrapper, // layers above playfield drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() From fef6e55b397130d90b055b2939088cddb346d3b8 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 19 Oct 2020 12:32:16 -0700 Subject: [PATCH 330/350] Remove unused using and field --- osu.Game/Screens/Play/HUDOverlay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index af5e2b80e1..0a45df51a0 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -16,7 +16,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; -using osu.Game.Skinning; using osuTK; using osuTK.Input; @@ -66,8 +65,6 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; - private readonly Container mainUIElements; - private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -92,7 +89,7 @@ namespace osu.Game.Screens.Play { new Drawable[] { - mainUIElements = new Container + new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 6e4b28ed1e69cdb3aaa574b035ce0dd040d82933 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 20 Oct 2020 00:32:44 +0300 Subject: [PATCH 331/350] Different version of epilepsy warning display --- .../Screens/Play/BeatmapMetadataDisplay.cs | 44 ------- osu.Game/Screens/Play/PlayerLoader.cs | 114 +++++++++++++++++- 2 files changed, 113 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index bab141a75e..5530b4beac 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -49,44 +49,6 @@ namespace osu.Game.Screens.Play } } - private class EpilepsyWarning : FillFlowContainer - { - public EpilepsyWarning(bool warning) - { - if (warning) - this.Show(); - else - this.Hide(); - - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Vertical; - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Icon = FontAwesome.Solid.ExclamationTriangle, - Size = new Vector2(40), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "This beatmap contains scenes with rapidly flashing colours.", - Font = OsuFont.GetFont(size: 20), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Please take caution if you are affected by epilepsy.", - Font = OsuFont.GetFont(size: 20), - } - }; - } - } - private readonly WorkingBeatmap beatmap; private readonly Bindable> mods; private readonly Drawable facade; @@ -201,12 +163,6 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 }, Current = mods }, - new EpilepsyWarning(beatmap.BeatmapInfo.EpilepsyWarning) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 20 }, - } }, } }; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 93a734589c..4c04627651 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -6,16 +6,21 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -84,6 +89,8 @@ namespace osu.Game.Screens.Play private bool hideOverlays; + private bool epilepsyShown; + private InputManager inputManager; private IdleTracker idleTracker; @@ -308,7 +315,27 @@ namespace osu.Game.Screens.Play { contentOut(); - this.Delay(250).Schedule(() => + if (true && !epilepsyShown) + { + EpilepsyWarning warning; + + AddInternal(warning = new EpilepsyWarning + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { Value = Visibility.Visible } + }); + + epilepsyShown = true; + + this.Delay(2000).Schedule(() => + { + warning.Hide(); + warning.Expire(); + }); + } + + this.Delay(epilepsyShown ? 2500 : 250).Schedule(() => { if (!this.IsCurrentScreen()) return; @@ -398,5 +425,90 @@ namespace osu.Game.Screens.Play } #endregion + + private class EpilepsyWarning : VisibilityContainer + { + private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); + + private Track track; + private FillFlowContainer warningContent; + + public EpilepsyWarning() + { + RelativeSizeAxes = Axes.Both; + Alpha = 0f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, IBindable beatmap) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + warningContent = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new SpriteIcon + { + Colour = colours.Yellow, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.ExclamationTriangle, + Size = new Vector2(50), + }, + new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 25)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.Centre, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }.With(tfc => + { + tfc.AddText("This beatmap contains scenes with "); + tfc.AddText("rapidly flashing colours", s => + { + s.Font = s.Font.With(weight: FontWeight.Bold); + s.Colour = colours.Yellow; + }); + tfc.AddText("."); + + tfc.NewParagraph(); + tfc.AddText("Please take caution if you are affected by epilepsy."); + }), + } + } + }; + + track = beatmap.Value.Track; + track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); + } + + protected override void PopIn() + { + this.FadeIn(500, Easing.InQuint) + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); + + warningContent.FadeIn(500, Easing.InQuint); + } + + protected override void PopOut() => this.FadeOut(500, Easing.InQuint); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); + } + } } } From 44279ed34741fc9a376735d45620bb371b44e79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:46:09 +0200 Subject: [PATCH 332/350] Remove unused using directive --- osu.Game/Screens/Play/PlayerLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 4c04627651..76020f5385 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -20,7 +20,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; From aeca61eb3ecf652ba1dab3ec0abc3bc935f535ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:48:02 +0200 Subject: [PATCH 333/350] Split warning to separate file --- osu.Game/Screens/Play/EpilepsyWarning.cs | 105 +++++++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 89 ------------------- 2 files changed, 105 insertions(+), 89 deletions(-) create mode 100644 osu.Game/Screens/Play/EpilepsyWarning.cs diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs new file mode 100644 index 0000000000..e115c7e057 --- /dev/null +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class EpilepsyWarning : VisibilityContainer + { + private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); + + private Track track; + private FillFlowContainer warningContent; + + public EpilepsyWarning() + { + RelativeSizeAxes = Axes.Both; + Alpha = 0f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, IBindable beatmap) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + warningContent = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new SpriteIcon + { + Colour = colours.Yellow, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.ExclamationTriangle, + Size = new Vector2(50), + }, + new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 25)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.Centre, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }.With(tfc => + { + tfc.AddText("This beatmap contains scenes with "); + tfc.AddText("rapidly flashing colours", s => + { + s.Font = s.Font.With(weight: FontWeight.Bold); + s.Colour = colours.Yellow; + }); + tfc.AddText("."); + + tfc.NewParagraph(); + tfc.AddText("Please take caution if you are affected by epilepsy."); + }), + } + } + }; + + track = beatmap.Value.Track; + track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); + } + + protected override void PopIn() + { + this.FadeIn(500, Easing.InQuint) + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); + + warningContent.FadeIn(500, Easing.InQuint); + } + + protected override void PopOut() => this.FadeOut(500, Easing.InQuint); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); + } + } +} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 76020f5385..801feb7d01 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -6,17 +6,13 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -424,90 +420,5 @@ namespace osu.Game.Screens.Play } #endregion - - private class EpilepsyWarning : VisibilityContainer - { - private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); - - private Track track; - private FillFlowContainer warningContent; - - public EpilepsyWarning() - { - RelativeSizeAxes = Axes.Both; - Alpha = 0f; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, IBindable beatmap) - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - warningContent = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new SpriteIcon - { - Colour = colours.Yellow, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.Solid.ExclamationTriangle, - Size = new Vector2(50), - }, - new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 25)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.Centre, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }.With(tfc => - { - tfc.AddText("This beatmap contains scenes with "); - tfc.AddText("rapidly flashing colours", s => - { - s.Font = s.Font.With(weight: FontWeight.Bold); - s.Colour = colours.Yellow; - }); - tfc.AddText("."); - - tfc.NewParagraph(); - tfc.AddText("Please take caution if you are affected by epilepsy."); - }), - } - } - }; - - track = beatmap.Value.Track; - track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); - } - - protected override void PopIn() - { - this.FadeIn(500, Easing.InQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); - - warningContent.FadeIn(500, Easing.InQuint); - } - - protected override void PopOut() => this.FadeOut(500, Easing.InQuint); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); - } - } } } From a9f27a71a2ff60b848632cf5595b354b49db760a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:53:41 +0200 Subject: [PATCH 334/350] Fix code formatting issues --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 6 +++--- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 341924ae6d..1efc71cad3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestPlayerLoaderContainer container; private TestPlayer player; - private bool EpilepsyWarning = false; + private bool epilepsyWarning; [Resolved] private AudioManager audioManager { get; set; } @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - Beatmap.Value.BeatmapInfo.EpilepsyWarning = EpilepsyWarning; + Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning; foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); @@ -247,7 +247,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(false)] public void TestEpilepsyWarning(bool warning) { - AddStep("change epilepsy warning", () => EpilepsyWarning = warning); + AddStep("change epilepsy warning", () => epilepsyWarning = warning); AddStep("load dummy beatmap", () => ResetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index fd17e38a4f..36a3880890 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -175,6 +175,7 @@ namespace osu.Game.Beatmaps.Formats case @"WidescreenStoryboard": beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; + case @"EpilepsyWarning": beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; break; From 850590304142d76ccd59d130765283ec7020ce44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:08:05 +0200 Subject: [PATCH 335/350] Move warning construction to load() --- osu.Game/Screens/Play/PlayerLoader.cs | 34 ++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 801feb7d01..8d66eb67d3 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -84,14 +85,15 @@ namespace osu.Game.Screens.Play private bool hideOverlays; - private bool epilepsyShown; - private InputManager inputManager; private IdleTracker idleTracker; private ScheduledDelegate scheduledPushPlayer; + [CanBeNull] + private EpilepsyWarning epilepsyWarning; + [Resolved(CanBeNull = true)] private NotificationOverlay notificationOverlay { get; set; } @@ -140,6 +142,15 @@ namespace osu.Game.Screens.Play }, idleTracker = new IdleTracker(750) }); + + if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) + { + AddInternal(epilepsyWarning = new EpilepsyWarning + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } } protected override void LoadComplete() @@ -310,27 +321,18 @@ namespace osu.Game.Screens.Play { contentOut(); - if (true && !epilepsyShown) + if (epilepsyWarning != null) { - EpilepsyWarning warning; - - AddInternal(warning = new EpilepsyWarning - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - State = { Value = Visibility.Visible } - }); - - epilepsyShown = true; + epilepsyWarning.State.Value = Visibility.Visible; this.Delay(2000).Schedule(() => { - warning.Hide(); - warning.Expire(); + epilepsyWarning.Hide(); + epilepsyWarning.Expire(); }); } - this.Delay(epilepsyShown ? 2500 : 250).Schedule(() => + this.Delay(epilepsyWarning?.State.Value == Visibility.Visible ? 2500 : 250).Schedule(() => { if (!this.IsCurrentScreen()) return; From 1ac0b3b13d5cd90028b2fae6596741a01178c949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:08:26 +0200 Subject: [PATCH 336/350] Add asserts to tests --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1efc71cad3..38eca72bd1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Overlays; @@ -249,7 +250,10 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("change epilepsy warning", () => epilepsyWarning = warning); AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType().Any() == warning); } private class TestPlayerLoaderContainer : Container From 6e50ae045834f85310fa2943a12bd17e69fcbee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:22:30 +0200 Subject: [PATCH 337/350] Reformulate push sequence code --- osu.Game/Screens/Play/PlayerLoader.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 8d66eb67d3..eabe090725 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; @@ -321,18 +322,24 @@ namespace osu.Game.Screens.Play { contentOut(); + TransformSequence pushSequence; + if (epilepsyWarning != null) { epilepsyWarning.State.Value = Visibility.Visible; - this.Delay(2000).Schedule(() => + pushSequence = this.Delay(3000).Schedule(() => { epilepsyWarning.Hide(); epilepsyWarning.Expire(); }); } + else + { + pushSequence = this.Delay(0); + } - this.Delay(epilepsyWarning?.State.Value == Visibility.Visible ? 2500 : 250).Schedule(() => + pushSequence.Delay(250).Schedule(() => { if (!this.IsCurrentScreen()) return; From 1238e6c30fc27f905bdf9274fe0feb976b07cd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:46:08 +0200 Subject: [PATCH 338/350] Add flag value to database Unfortunately required, as loadBeatmaps() refreshes the decoded beatmap with the database-stored values, which can end up overwriting the decoded ones. --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 - ...01019224408_AddEpilepsyWarning.Designer.cs | 508 ++++++++++++++++++ .../20201019224408_AddEpilepsyWarning.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 2 + 4 files changed, 533 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index b7946d53ca..1512240f8a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -90,8 +90,6 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } - - [NotMapped] public bool EpilepsyWarning { get; set; } // Editor diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs new file mode 100644 index 0000000000..1c05de832e --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20201019224408_AddEpilepsyWarning")] + partial class AddEpilepsyWarning + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs new file mode 100644 index 0000000000..be6968aa5d --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddEpilepsyWarning : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index bc4fc3342d..ec4461ca56 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -57,6 +57,8 @@ namespace osu.Game.Migrations b.Property("DistanceSpacing"); + b.Property("EpilepsyWarning"); + b.Property("GridSize"); b.Property("Hash"); From a164d330e57744b80f0834dfa8d5601c4baab9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:51:31 +0200 Subject: [PATCH 339/350] Improve feel of transition --- osu.Game/Screens/Play/EpilepsyWarning.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index e115c7e057..244a903bdd 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -89,12 +89,12 @@ namespace osu.Game.Screens.Play protected override void PopIn() { this.FadeIn(500, Easing.InQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InSine); warningContent.FadeIn(500, Easing.InQuint); } - protected override void PopOut() => this.FadeOut(500, Easing.InQuint); + protected override void PopOut() => this.FadeOut(500, Easing.OutQuint); protected override void Dispose(bool isDisposing) { From 1fc22bdbffdace861ad701520e6cc7d44055d5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 00:59:30 +0200 Subject: [PATCH 340/350] Only show warning once on given map --- osu.Game/Screens/Play/PlayerLoader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index eabe090725..fb03f09d8e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -324,7 +324,9 @@ namespace osu.Game.Screens.Play TransformSequence pushSequence; - if (epilepsyWarning != null) + // only show if the warning was created (i.e. the beatmap needs it) + // and this is not a restart of the map (the warning expires after first load). + if (epilepsyWarning?.IsAlive == true) { epilepsyWarning.State.Value = Visibility.Visible; From 05251c646ed52a55027d558136f7f4a48a967ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 01:06:20 +0200 Subject: [PATCH 341/350] Fade volume back up on pop out --- osu.Game/Screens/Play/EpilepsyWarning.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 244a903bdd..051604f115 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -89,12 +89,14 @@ namespace osu.Game.Screens.Play protected override void PopIn() { this.FadeIn(500, Easing.InQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InSine); + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); warningContent.FadeIn(500, Easing.InQuint); } - protected override void PopOut() => this.FadeOut(500, Easing.OutQuint); + protected override void PopOut() + => this.FadeOut(500, Easing.OutQuint) + .TransformBindableTo(trackVolumeOnEpilepsyWarning, 1, 500, Easing.OutQuint); protected override void Dispose(bool isDisposing) { From c57fecd1fc4379b7d15e58c5612e8c425ddea375 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 12:43:57 +0900 Subject: [PATCH 342/350] Update comment to make it clear this is a hack --- osu.Game/Screens/Play/HUDOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 0a45df51a0..6425f8123d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -205,7 +205,9 @@ namespace osu.Game.Screens.Play { base.Update(); - // for now align with the accuracy counter. eventually this will be user customisable. + // HACK: for now align with the accuracy counter. + // this is done for the sake of hacky legacy skins which extend the health bar to take up the full screen area. + // it only works with the default skin due to padding offsetting it *just enough* to coexist. topRightElements.Y = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; bottomRightElements.Y = -Progress.Height; From 267b399f9f120f1d92500f775fbbbb949ee0290d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 13:59:03 +0900 Subject: [PATCH 343/350] Add some simple border styles --- .../Configuration/OsuRulesetConfigManager.cs | 5 +- .../Edit/OsuHitObjectComposer.cs | 14 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 8 +- .../UI/OsuSettingsSubsection.cs | 7 +- osu.Game/Rulesets/UI/PlayfieldBorder.cs | 137 ++++++++++++++++-- osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs | 12 ++ 6 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index da8767c017..e8272057f3 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -3,6 +3,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Configuration { @@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Configuration Set(OsuRulesetSetting.SnakingInSliders, true); Set(OsuRulesetSetting.SnakingOutSliders, true); Set(OsuRulesetSetting.ShowCursorTrail, true); - Set(OsuRulesetSetting.ShowPlayfieldBorder, false); + Set(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); } } @@ -28,6 +29,6 @@ namespace osu.Game.Rulesets.Osu.Configuration SnakingInSliders, SnakingOutSliders, ShowCursorTrail, - ShowPlayfieldBorder, + PlayfieldBorderStyle, } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index d9de9d2c5d..edd684d886 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -56,12 +56,18 @@ namespace osu.Game.Rulesets.Osu.Edit [BackgroundDependencyLoader] private void load() { - LayerBelowRuleset.Add(new PlayfieldBorder + LayerBelowRuleset.AddRange(new Drawable[] { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } + new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both, + PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } + }, + distanceSnapGridContainer = new Container + { + RelativeSizeAxes = Axes.Both + } }); - LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 26fe686b0d..50727d590a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private readonly Bindable showPlayfieldBorder = new BindableBool(); + private readonly Bindable playfieldBorderStyle = new BindableBool(); private readonly IDictionary> poolDictionary = new Dictionary>(); @@ -90,13 +90,9 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader(true)] private void load(OsuRulesetConfigManager config) { - config?.BindWith(OsuRulesetSetting.ShowPlayfieldBorder, showPlayfieldBorder); - showPlayfieldBorder.BindValueChanged(updateBorderVisibility, true); + config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle); } - private void updateBorderVisibility(ValueChangedEvent settingChange) - => playfieldBorder.State.Value = settingChange.NewValue ? Visibility.Visible : Visibility.Hidden; - public override void Add(DrawableHitObject h) { h.OnNewResult += onNewResult; diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 28c609f412..705ba3e929 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { @@ -39,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.UI LabelText = "Cursor trail", Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) }, - new SettingsCheckbox + new SettingsEnumDropdown { - LabelText = "Show playfield border", - Current = config.GetBindable(OsuRulesetSetting.ShowPlayfieldBorder), + LabelText = "Playfield border style", + Current = config.GetBindable(OsuRulesetSetting.PlayfieldBorderStyle), }, }; } diff --git a/osu.Game/Rulesets/UI/PlayfieldBorder.cs b/osu.Game/Rulesets/UI/PlayfieldBorder.cs index 66371c89ad..c83d1e7842 100644 --- a/osu.Game/Rulesets/UI/PlayfieldBorder.cs +++ b/osu.Game/Rulesets/UI/PlayfieldBorder.cs @@ -1,9 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.UI @@ -11,27 +15,136 @@ namespace osu.Game.Rulesets.UI /// /// Provides a border around the playfield. /// - public class PlayfieldBorder : VisibilityContainer + public class PlayfieldBorder : CompositeDrawable { - private const int fade_duration = 200; + public Bindable PlayfieldBorderStyle { get; } = new Bindable(); + + private const int fade_duration = 500; + + private const float corner_length = 0.05f; + private const float corner_thickness = 2; public PlayfieldBorder() { RelativeSizeAxes = Axes.Both; - Masking = true; - BorderColour = Color4.White; - BorderThickness = 2; - - InternalChild = new Box + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true + new Line(Direction.Horizontal) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + new Line(Direction.Horizontal) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + new Line(Direction.Horizontal) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + new Line(Direction.Horizontal) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + new Line(Direction.Vertical) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + new Line(Direction.Vertical) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + new Line(Direction.Vertical) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + new Line(Direction.Vertical) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, }; } - protected override void PopIn() => this.FadeIn(fade_duration, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(fade_duration, Easing.OutQuint); + protected override void LoadComplete() + { + base.LoadComplete(); + + PlayfieldBorderStyle.BindValueChanged(updateStyle, true); + } + + private void updateStyle(ValueChangedEvent style) + { + switch (style.NewValue) + { + case UI.PlayfieldBorderStyle.None: + this.FadeOut(fade_duration, Easing.OutQuint); + foreach (var line in InternalChildren.OfType()) + line.TweenLength(0); + + break; + + case UI.PlayfieldBorderStyle.Corners: + this.FadeIn(fade_duration, Easing.OutQuint); + foreach (var line in InternalChildren.OfType()) + line.TweenLength(corner_length); + + break; + + case UI.PlayfieldBorderStyle.Full: + this.FadeIn(fade_duration, Easing.OutQuint); + foreach (var line in InternalChildren.OfType()) + line.TweenLength(0.5f); + + break; + } + } + + private class Line : Box + { + private readonly Direction direction; + + public Line(Direction direction) + { + this.direction = direction; + + Colour = Color4.White; + // starting in relative avoids the framework thinking it knows best and setting the width to 1 initially. + + switch (direction) + { + case Direction.Horizontal: + RelativeSizeAxes = Axes.X; + Size = new Vector2(0, corner_thickness); + break; + + case Direction.Vertical: + RelativeSizeAxes = Axes.Y; + Size = new Vector2(corner_thickness, 0); + break; + } + } + + public void TweenLength(float value) + { + switch (direction) + { + case Direction.Horizontal: + this.ResizeWidthTo(value, fade_duration, Easing.OutQuint); + break; + + case Direction.Vertical: + this.ResizeHeightTo(value, fade_duration, Easing.OutQuint); + break; + } + } + } } } diff --git a/osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs b/osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs new file mode 100644 index 0000000000..0a0aad884e --- /dev/null +++ b/osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.UI +{ + public enum PlayfieldBorderStyle + { + None, + Corners, + Full + } +} From f9ca47ca86a2d7e40f6b05a46e3e9ddf5d85f37e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 13:59:07 +0900 Subject: [PATCH 344/350] Add test coverage --- .../TestPlayfieldBorder.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs b/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs new file mode 100644 index 0000000000..23d9d265be --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestPlayfieldBorder : OsuTestScene + { + public TestPlayfieldBorder() + { + Bindable playfieldBorderStyle = new Bindable(); + + AddStep("add drawables", () => + { + Child = new Container + { + Size = new Vector2(400, 300), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new PlayfieldBorder + { + PlayfieldBorderStyle = { BindTarget = playfieldBorderStyle } + } + } + }; + }); + + AddStep("Set none", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.None); + AddStep("Set corners", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.Corners); + AddStep("Set full", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.Full); + } + } +} From 024009e1749aee8c7a147c1ac2645080b88b25f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 14:19:15 +0900 Subject: [PATCH 345/350] Change default to "always visible" --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e95f8b571a..7d601c0cb9 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -90,7 +90,7 @@ namespace osu.Game.Configuration Set(OsuSetting.HitLighting, true); - Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.DuringGameplay); + Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.FadePlayfieldWhenHealthLow, true); From 4f8a755518d45ba9d4c40b717e0729cb8ebbac82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 14:20:44 +0900 Subject: [PATCH 346/350] Add "hide during gameplay" mode --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- osu.Game/Configuration/HUDVisibilityMode.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 55d6b38b66..6ec673704c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExternalHideDoesntAffectConfig() { - HUDVisibilityMode originalConfigValue = HUDVisibilityMode.DuringGameplay; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; createNew(); diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs index 2b133b1bcf..b0b55dd811 100644 --- a/osu.Game/Configuration/HUDVisibilityMode.cs +++ b/osu.Game/Configuration/HUDVisibilityMode.cs @@ -9,8 +9,11 @@ namespace osu.Game.Configuration { Never, + [Description("Hide during gameplay")] + HideDuringGameplay, + [Description("Hide during breaks")] - DuringGameplay, + HideDuringBreaks, Always } From 4e57751ca1ebe5284a58d783bea35c6786e7b8df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 15:03:12 +0900 Subject: [PATCH 347/350] Fix background dim application to avoid overdraw, transition smoother --- osu.Game/Screens/Play/EpilepsyWarning.cs | 18 +++++++----------- osu.Game/Screens/Play/PlayerLoader.cs | 3 +++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 051604f115..960d549ab6 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -5,16 +5,14 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Screens.Backgrounds; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -31,16 +29,13 @@ namespace osu.Game.Screens.Play Alpha = 0f; } + public BackgroundScreenBeatmap DimmableBackground { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, IBindable beatmap) { Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, warningContent = new FillFlowContainer { Anchor = Anchor.Centre, @@ -88,10 +83,11 @@ namespace osu.Game.Screens.Play protected override void PopIn() { - this.FadeIn(500, Easing.InQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, 500, Easing.InQuint); + this.TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, FADE_DURATION); - warningContent.FadeIn(500, Easing.InQuint); + DimmableBackground?.FadeColour(OsuColour.Gray(0.5f), FADE_DURATION, Easing.OutQuint); + + this.FadeIn(FADE_DURATION, Easing.OutQuint); } protected override void PopOut() diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index f68246c928..ef01072a62 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -167,6 +167,9 @@ namespace osu.Game.Screens.Play { base.OnEntering(last); + if (epilepsyWarning != null) + epilepsyWarning.DimmableBackground = Background; + content.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); From 7a68636f71195e9003e84d52b3f1e598e3089ad4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 15:03:33 +0900 Subject: [PATCH 348/350] Adjust fade sequence and durations to feel better --- osu.Game/Screens/Play/EpilepsyWarning.cs | 6 +++--- osu.Game/Screens/Play/PlayerLoader.cs | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 960d549ab6..1c8e8c2f4e 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -18,6 +18,8 @@ namespace osu.Game.Screens.Play { public class EpilepsyWarning : VisibilityContainer { + public const double FADE_DURATION = 500; + private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); private Track track; @@ -90,9 +92,7 @@ namespace osu.Game.Screens.Play this.FadeIn(FADE_DURATION, Easing.OutQuint); } - protected override void PopOut() - => this.FadeOut(500, Easing.OutQuint) - .TransformBindableTo(trackVolumeOnEpilepsyWarning, 1, 500, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(FADE_DURATION); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ef01072a62..fae0bfb295 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -323,26 +323,29 @@ namespace osu.Game.Screens.Play { contentOut(); - TransformSequence pushSequence; + TransformSequence pushSequence = this.Delay(250); // only show if the warning was created (i.e. the beatmap needs it) // and this is not a restart of the map (the warning expires after first load). if (epilepsyWarning?.IsAlive == true) { - epilepsyWarning.State.Value = Visibility.Visible; + const double epilepsy_display_length = 3000; - pushSequence = this.Delay(3000).Schedule(() => + pushSequence.Schedule(() => { - epilepsyWarning.Hide(); - epilepsyWarning.Expire(); + epilepsyWarning.State.Value = Visibility.Visible; + + this.Delay(epilepsy_display_length).Schedule(() => + { + epilepsyWarning.Hide(); + epilepsyWarning.Expire(); + }); }); - } - else - { - pushSequence = this.Delay(0); + + pushSequence.Delay(epilepsy_display_length); } - pushSequence.Delay(250).Schedule(() => + pushSequence.Schedule(() => { if (!this.IsCurrentScreen()) return; From 411ae38605bd601336de5f01e88e48e65d2453be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 15:06:31 +0900 Subject: [PATCH 349/350] Remove unused using --- osu.Game/Rulesets/UI/PlayfieldBorder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/PlayfieldBorder.cs b/osu.Game/Rulesets/UI/PlayfieldBorder.cs index c83d1e7842..458b88c6db 100644 --- a/osu.Game/Rulesets/UI/PlayfieldBorder.cs +++ b/osu.Game/Rulesets/UI/PlayfieldBorder.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; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; From 55d08fad5cdca600405551729126dbfe2634f687 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Oct 2020 15:18:15 +0900 Subject: [PATCH 350/350] Remove unused field --- osu.Game/Screens/Play/EpilepsyWarning.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 1c8e8c2f4e..e3cf0cd227 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -23,7 +23,6 @@ namespace osu.Game.Screens.Play private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); private Track track; - private FillFlowContainer warningContent; public EpilepsyWarning() { @@ -38,7 +37,7 @@ namespace osu.Game.Screens.Play { Children = new Drawable[] { - warningContent = new FillFlowContainer + new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre,