From ea29f7c34435635b1b9f2a3e5a12acdefce1d100 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 4 Mar 2020 17:01:37 +0100 Subject: [PATCH 001/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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/326] 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 3a97ee4712557862fba5cecb1d625ae684d2fbee Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 9 Aug 2020 16:16:01 -0700 Subject: [PATCH 058/326] 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 059/326] 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 060/326] 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 f98e96e45b22b3b34e56543f2249d43e62585d5f Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 10:52:23 +0930 Subject: [PATCH 061/326] Add osu!-specific enum for confine mouse mode --- osu.Game/Input/OsuConfineMouseMode.cs | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 osu.Game/Input/OsuConfineMouseMode.cs diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs new file mode 100644 index 0000000000..32b456395c --- /dev/null +++ b/osu.Game/Input/OsuConfineMouseMode.cs @@ -0,0 +1,37 @@ +// 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; +using osu.Framework.Input; + +namespace osu.Game.Input +{ + /// + /// Determines the situations in which the mouse cursor should be confined to the window. + /// Expands upon by providing the option to confine during gameplay. + /// + public enum OsuConfineMouseMode + { + /// + /// The mouse cursor will be free to move outside the game window. + /// + Never, + + /// + /// The mouse cursor will be locked to the window bounds while in fullscreen mode. + /// + Fullscreen, + + /// + /// The mouse cursor will be locked to the window bounds during gameplay, + /// but may otherwise move freely. + /// + [Description("During Gameplay")] + DuringGameplay, + + /// + /// The mouse cursor will always be locked to the window bounds while the game has focus. + /// + Always + } +} From 322d179076a383cf7fd2e7506e27189fba025278 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 11:04:28 +0930 Subject: [PATCH 062/326] Replace settings item with osu! confine cursor mode --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a8a8794320..9ef846c974 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -6,6 +6,7 @@ using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions; using osu.Framework.Platform; +using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select; @@ -66,6 +67,7 @@ namespace osu.Game.Configuration Set(OsuSetting.MouseDisableButtons, false); Set(OsuSetting.MouseDisableWheel, false); + Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); // Graphics Set(OsuSetting.ShowFpsDisplay, false); @@ -191,6 +193,7 @@ namespace osu.Game.Configuration FadePlayfieldWhenHealthLow, MouseDisableButtons, MouseDisableWheel, + ConfineMouseMode, AudioOffset, VolumeInactive, MenuMusic, diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index d27ab63fb7..0d98508e3b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -6,9 +6,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; +using osu.Game.Input; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -47,10 +47,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Map absolute input to window", Bindable = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) }, - new SettingsEnumDropdown + new SettingsEnumDropdown { LabelText = "Confine mouse cursor to window", - Bindable = config.GetBindable(FrameworkSetting.ConfineMouseMode), + Bindable = osuConfig.GetBindable(OsuSetting.ConfineMouseMode) }, new SettingsCheckbox { From ef3c8fa21f8105ec181be6392bd65c929a597f40 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 11:38:35 +0930 Subject: [PATCH 063/326] Add tracking component to handle OsuConfineMouseMode --- osu.Game/Input/ConfineMouseTracker.cs | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game/Input/ConfineMouseTracker.cs diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs new file mode 100644 index 0000000000..b111488a5b --- /dev/null +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -0,0 +1,72 @@ +// 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.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Game.Configuration; +using osu.Game.Screens.Play; + +namespace osu.Game.Input +{ + /// + /// Connects with + /// while providing a property for to indicate whether gameplay is currently active. + /// + public class ConfineMouseTracker : Component + { + private Bindable frameworkConfineMode; + private Bindable osuConfineMode; + + private bool gameplayActive; + + /// + /// Indicates whether osu! is currently considered "in gameplay" for the + /// purposes of . + /// + public bool GameplayActive + { + get => gameplayActive; + set + { + if (gameplayActive == value) + return; + + gameplayActive = value; + updateConfineMode(); + } + } + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) + { + frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); + osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); + osuConfineMode.BindValueChanged(_ => updateConfineMode(), true); + } + + private void updateConfineMode() + { + switch (osuConfineMode.Value) + { + case OsuConfineMouseMode.Never: + frameworkConfineMode.Value = ConfineMouseMode.Never; + break; + + case OsuConfineMouseMode.Fullscreen: + frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; + break; + + case OsuConfineMouseMode.DuringGameplay: + frameworkConfineMode.Value = GameplayActive ? ConfineMouseMode.Always : ConfineMouseMode.Never; + break; + + case OsuConfineMouseMode.Always: + frameworkConfineMode.Value = ConfineMouseMode.Always; + break; + } + } + } +} From 00f15231bc78a6e7830694bf41cbc1db331e505f Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 21:52:39 +0930 Subject: [PATCH 064/326] Cache ConfineMouseTracker --- osu.Game/OsuGame.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 053eb01dcd..7358918758 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -88,6 +88,8 @@ namespace osu.Game private IdleTracker idleTracker; + private ConfineMouseTracker confineMouseTracker; + public readonly Bindable OverlayActivationMode = new Bindable(); protected OsuScreenStack ScreenStack; @@ -553,6 +555,7 @@ namespace osu.Game BackButton.Receptor receptor; dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); + dependencies.Cache(confineMouseTracker = new ConfineMouseTracker()); AddRange(new Drawable[] { @@ -588,7 +591,8 @@ namespace osu.Game rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, - idleTracker + idleTracker, + confineMouseTracker }); ScreenStack.ScreenPushed += screenPushed; From 85b3fff9c8a695d89453b0dbd1b55eb0d27fe5e4 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 23:11:09 +0930 Subject: [PATCH 065/326] Update mouse confine when gameplay state changes --- osu.Game/Screens/Play/Player.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..3b8c4aea01 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -18,6 +18,7 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; +using osu.Game.Input; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; @@ -63,6 +64,9 @@ namespace osu.Game.Screens.Play private Bindable mouseWheelDisabled; + [Resolved(CanBeNull = true)] + private ConfineMouseTracker confineMouseTracker { get; set; } + private readonly Bindable storyboardReplacesBackground = new Bindable(); public int RestartCount; @@ -197,10 +201,15 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => + { + updatePauseOnFocusLostState(); + updateConfineMouse(); + }, true); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); + DrawableRuleset.IsPaused.ValueChanged += _ => updateConfineMouse(); DrawableRuleset.OnNewResult += r => { @@ -346,6 +355,12 @@ namespace osu.Game.Screens.Play && !DrawableRuleset.HasReplayLoaded.Value && !breakTracker.IsBreakTime.Value; + private void updateConfineMouse() + { + if (confineMouseTracker != null) + confineMouseTracker.GameplayActive = !GameplayClockContainer.IsPaused.Value && !DrawableRuleset.HasReplayLoaded.Value && !HasFailed; + } + private IBeatmap loadPlayableBeatmap() { IBeatmap playable; @@ -379,7 +394,7 @@ namespace osu.Game.Screens.Play } catch (Exception e) { - Logger.Error(e, "Could not load beatmap sucessfully!"); + Logger.Error(e, "Could not load beatmap successfully!"); //couldn't load, hard abort! return null; } From 6ff26f6b8c943e1178a5e4ebf78c086501cc75cb Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 24 Sep 2020 12:52:42 -0700 Subject: [PATCH 066/326] 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 067/326] 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 068/326] 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 069/326] 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 5859755886ac3e141e00e72a421bf61d19d6524e Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Mon, 5 Oct 2020 11:11:46 +1030 Subject: [PATCH 070/326] Use current OverlayActivationMode to determine confine logic --- osu.Game/Input/ConfineMouseTracker.cs | 33 +++++++++++---------------- osu.Game/Screens/Play/Player.cs | 5 ++-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index b111488a5b..6565967d1d 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -7,44 +7,37 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Configuration; +using osu.Game.Overlays; using osu.Game.Screens.Play; namespace osu.Game.Input { /// - /// Connects with - /// while providing a property for to indicate whether gameplay is currently active. + /// Connects with , + /// while optionally binding an mode, usually that of the current . + /// It is assumed that while overlay activation is , we should also confine the + /// mouse cursor if it has been requested with . /// public class ConfineMouseTracker : Component { private Bindable frameworkConfineMode; private Bindable osuConfineMode; - private bool gameplayActive; - /// - /// Indicates whether osu! is currently considered "in gameplay" for the - /// purposes of . + /// The bindable used to indicate whether gameplay is active. + /// Should be bound to the corresponding bindable of the current . + /// Defaults to to assume that all other screens are considered "not gameplay". /// - public bool GameplayActive - { - get => gameplayActive; - set - { - if (gameplayActive == value) - return; - - gameplayActive = value; - updateConfineMode(); - } - } + public IBindable OverlayActivationMode { get; } = new Bindable(OverlayActivation.All); [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); - osuConfineMode.BindValueChanged(_ => updateConfineMode(), true); + osuConfineMode.ValueChanged += _ => updateConfineMode(); + + OverlayActivationMode.BindValueChanged(_ => updateConfineMode(), true); } private void updateConfineMode() @@ -60,7 +53,7 @@ namespace osu.Game.Input break; case OsuConfineMouseMode.DuringGameplay: - frameworkConfineMode.Value = GameplayActive ? ConfineMouseMode.Always : ConfineMouseMode.Never; + frameworkConfineMode.Value = OverlayActivationMode.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 77f873083a..6d2f61bdf1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -229,6 +229,8 @@ namespace osu.Game.Screens.Play DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); + confineMouseTracker.OverlayActivationMode.BindTo(OverlayActivationMode); + // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -365,9 +367,6 @@ namespace osu.Game.Screens.Play OverlayActivationMode.Value = OverlayActivation.UserTriggered; else OverlayActivationMode.Value = OverlayActivation.Disabled; - - if (confineMouseTracker != null) - confineMouseTracker.GameplayActive = !GameplayClockContainer.IsPaused.Value && !DrawableRuleset.HasReplayLoaded.Value && !HasFailed; } private void updatePauseOnFocusLostState() => From a483dfd2d7157131d886d8b7c92a4b08defdbf63 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Mon, 5 Oct 2020 11:54:39 +1030 Subject: [PATCH 071/326] Allow confineMouseTracker to be null --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6d2f61bdf1..de67b2d46d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Play DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); - confineMouseTracker.OverlayActivationMode.BindTo(OverlayActivationMode); + confineMouseTracker?.OverlayActivationMode.BindTo(OverlayActivationMode); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); From 22b0105d629a70344609d33314a76978053e633d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 13:00:02 +0900 Subject: [PATCH 072/326] Show a notification if checking for updates via button and there are none available --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 8 ++++--- .../Sections/General/UpdateSettings.cs | 21 +++++++++++++++++-- osu.Game/Updater/SimpleUpdateManager.cs | 7 ++++++- osu.Game/Updater/UpdateManager.cs | 19 ++++++++++------- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 05c8e835ac..b9b148b383 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -37,9 +37,9 @@ namespace osu.Desktop.Updater Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); } - protected override async Task PerformUpdateCheck() => await checkForUpdateAsync(); + protected override async Task PerformUpdateCheck() => await checkForUpdateAsync(); - private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) + private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { // should we schedule a retry on completion of this check? bool scheduleRecheck = true; @@ -51,7 +51,7 @@ namespace osu.Desktop.Updater var info = await updateManager.CheckForUpdate(!useDeltaPatching); if (info.ReleasesToApply.Count == 0) // no updates available. bail and retry later. - return; + return false; if (notification == null) { @@ -103,6 +103,8 @@ namespace osu.Desktop.Updater Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30); } } + + return true; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 9c7d0b0be4..9b7b7392d8 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -4,9 +4,11 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; +using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Updater; @@ -21,6 +23,9 @@ namespace osu.Game.Overlays.Settings.Sections.General private SettingsButton checkForUpdatesButton; + [Resolved] + private NotificationOverlay notifications { get; set; } + [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuConfigManager config, OsuGame game) { @@ -30,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - if (updateManager?.CanCheckForUpdate == true) + //if (updateManager?.CanCheckForUpdate == true) { Add(checkForUpdatesButton = new SettingsButton { @@ -38,7 +43,19 @@ namespace osu.Game.Overlays.Settings.Sections.General Action = () => { checkForUpdatesButton.Enabled.Value = false; - Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => checkForUpdatesButton.Enabled.Value = true)); + Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => + { + if (!t.Result) + { + notifications.Post(new SimpleNotification + { + Text = $"You are running the latest release ({game.Version})", + Icon = FontAwesome.Solid.CheckCircle, + }); + } + + checkForUpdatesButton.Enabled.Value = true; + })); } }); } diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index ebb9995c66..79b2d46b93 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Updater version = game.Version; } - protected override async Task PerformUpdateCheck() + protected override async Task PerformUpdateCheck() { try { @@ -53,12 +53,17 @@ namespace osu.Game.Updater return true; } }); + + return true; } } catch { // we shouldn't crash on a web failure. or any failure for the matter. + return true; } + + return false; } private string getBestUrl(GitHubRelease release) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 61775a26b7..30e28f0e95 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -57,25 +57,28 @@ namespace osu.Game.Updater private readonly object updateTaskLock = new object(); - private Task updateCheckTask; + private Task updateCheckTask; - public async Task CheckForUpdateAsync() + public async Task CheckForUpdateAsync() { - if (!CanCheckForUpdate) - return; - - Task waitTask; + Task waitTask; lock (updateTaskLock) waitTask = (updateCheckTask ??= PerformUpdateCheck()); - await waitTask; + bool hasUpdates = await waitTask; lock (updateTaskLock) updateCheckTask = null; + + return hasUpdates; } - protected virtual Task PerformUpdateCheck() => Task.CompletedTask; + /// + /// Performs an asynchronous check for application updates. + /// + /// Whether any update is waiting. May return true if an error occured (there is potentially an update available). + protected virtual Task PerformUpdateCheck() => Task.FromResult(false); private class UpdateCompleteNotification : SimpleNotification { From de47392e3d11ddf7e7831c029ac4c5bf353007d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 13:18:42 +0900 Subject: [PATCH 073/326] Display the "restart to update" notification on checking for update after dismissal --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 50 ++++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index b9b148b383..71f9fafe57 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -29,6 +29,11 @@ namespace osu.Desktop.Updater private static readonly Logger logger = Logger.GetLogger("updater"); + /// + /// Whether an update has been downloaded but not yet applied. + /// + private bool updatePending; + [BackgroundDependencyLoader] private void load(NotificationOverlay notification) { @@ -49,9 +54,19 @@ namespace osu.Desktop.Updater updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); var info = await updateManager.CheckForUpdate(!useDeltaPatching); + if (info.ReleasesToApply.Count == 0) + { + if (updatePending) + { + // the user may have dismissed the completion notice, so show it again. + notificationOverlay.Post(new UpdateCompleteNotification(this)); + return true; + } + // no updates available. bail and retry later. return false; + } if (notification == null) { @@ -72,6 +87,7 @@ namespace osu.Desktop.Updater await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f); notification.State = ProgressNotificationState.Completed; + updatePending = true; } catch (Exception e) { @@ -113,10 +129,27 @@ namespace osu.Desktop.Updater updateManager?.Dispose(); } + private class UpdateCompleteNotification : ProgressCompletionNotification + { + [Resolved] + private OsuGame game { get; set; } + + public UpdateCompleteNotification(SquirrelUpdateManager updateManager) + { + Text = @"Update ready to install. Click to restart!"; + + Activated = () => + { + updateManager.PrepareUpdateAsync() + .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit())); + return true; + }; + } + } + private class UpdateProgressNotification : ProgressNotification { private readonly SquirrelUpdateManager updateManager; - private OsuGame game; public UpdateProgressNotification(SquirrelUpdateManager updateManager) { @@ -125,23 +158,12 @@ namespace osu.Desktop.Updater protected override Notification CreateCompletionNotification() { - return new ProgressCompletionNotification - { - Text = @"Update ready to install. Click to restart!", - Activated = () => - { - updateManager.PrepareUpdateAsync() - .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit())); - return true; - } - }; + return new UpdateCompleteNotification(updateManager); } [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuGame game) + private void load(OsuColour colours) { - this.game = game; - IconContent.AddRange(new Drawable[] { new Box From 1877312a917ce017b5fa8e58951a4c373cb7cecb Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 6 Oct 2020 19:52:18 +1030 Subject: [PATCH 074/326] Rename DuringGameplay --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Input/ConfineMouseTracker.cs | 4 ++-- osu.Game/Input/OsuConfineMouseMode.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 78179a781a..71cbdb345c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -70,7 +70,7 @@ namespace osu.Game.Configuration Set(OsuSetting.MouseDisableButtons, false); Set(OsuSetting.MouseDisableWheel, false); - Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); + Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.WhenOverlaysDisabled); // Graphics Set(OsuSetting.ShowFpsDisplay, false); diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 6565967d1d..653622e881 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -16,7 +16,7 @@ namespace osu.Game.Input /// Connects with , /// while optionally binding an mode, usually that of the current . /// It is assumed that while overlay activation is , we should also confine the - /// mouse cursor if it has been requested with . + /// mouse cursor if it has been requested with . /// public class ConfineMouseTracker : Component { @@ -52,7 +52,7 @@ namespace osu.Game.Input frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; break; - case OsuConfineMouseMode.DuringGameplay: + case OsuConfineMouseMode.WhenOverlaysDisabled: frameworkConfineMode.Value = OverlayActivationMode.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs index 32b456395c..53e352c8bd 100644 --- a/osu.Game/Input/OsuConfineMouseMode.cs +++ b/osu.Game/Input/OsuConfineMouseMode.cs @@ -27,7 +27,7 @@ namespace osu.Game.Input /// but may otherwise move freely. /// [Description("During Gameplay")] - DuringGameplay, + WhenOverlaysDisabled, /// /// The mouse cursor will always be locked to the window bounds while the game has focus. From 782fc1d60fe1c10768ef393ba3102a77e3237662 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 6 Oct 2020 20:11:48 +1030 Subject: [PATCH 075/326] Use OsuGame.OverlayActivationMode rather than per-Player --- osu.Game/Input/ConfineMouseTracker.cs | 19 ++++++------------- osu.Game/Input/OsuConfineMouseMode.cs | 2 +- osu.Game/Screens/Play/Player.cs | 6 ------ 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 653622e881..3b54c03bb0 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -8,13 +8,12 @@ using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Overlays; -using osu.Game.Screens.Play; namespace osu.Game.Input { /// /// Connects with , - /// while optionally binding an mode, usually that of the current . + /// while binding . /// It is assumed that while overlay activation is , we should also confine the /// mouse cursor if it has been requested with . /// @@ -22,22 +21,16 @@ namespace osu.Game.Input { private Bindable frameworkConfineMode; private Bindable osuConfineMode; - - /// - /// The bindable used to indicate whether gameplay is active. - /// Should be bound to the corresponding bindable of the current . - /// Defaults to to assume that all other screens are considered "not gameplay". - /// - public IBindable OverlayActivationMode { get; } = new Bindable(OverlayActivation.All); + private IBindable overlayActivationMode; [BackgroundDependencyLoader] - private void load(FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) + private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); osuConfineMode.ValueChanged += _ => updateConfineMode(); - - OverlayActivationMode.BindValueChanged(_ => updateConfineMode(), true); + overlayActivationMode = game.OverlayActivationMode.GetBoundCopy(); + overlayActivationMode.BindValueChanged(_ => updateConfineMode(), true); } private void updateConfineMode() @@ -53,7 +46,7 @@ namespace osu.Game.Input break; case OsuConfineMouseMode.WhenOverlaysDisabled: - frameworkConfineMode.Value = OverlayActivationMode.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; + frameworkConfineMode.Value = overlayActivationMode?.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs index 53e352c8bd..e8411a3d9f 100644 --- a/osu.Game/Input/OsuConfineMouseMode.cs +++ b/osu.Game/Input/OsuConfineMouseMode.cs @@ -23,7 +23,7 @@ namespace osu.Game.Input Fullscreen, /// - /// The mouse cursor will be locked to the window bounds during gameplay, + /// The mouse cursor will be locked to the window bounds while overlays are disabled, /// but may otherwise move freely. /// [Description("During Gameplay")] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index de67b2d46d..175722c44e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -18,7 +18,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; -using osu.Game.Input; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; @@ -67,9 +66,6 @@ namespace osu.Game.Screens.Play private Bindable mouseWheelDisabled; - [Resolved(CanBeNull = true)] - private ConfineMouseTracker confineMouseTracker { get; set; } - private readonly Bindable storyboardReplacesBackground = new Bindable(); public int RestartCount; @@ -229,8 +225,6 @@ namespace osu.Game.Screens.Play DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); - confineMouseTracker?.OverlayActivationMode.BindTo(OverlayActivationMode); - // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); From e8b34ba4acd0d5fb61e06bb3f1ce4c14bffdded4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 20:57:39 +0900 Subject: [PATCH 076/326] Fix incorrectly committed testing change --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 9b7b7392d8..c216d1efe9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - //if (updateManager?.CanCheckForUpdate == true) + if (updateManager?.CanCheckForUpdate == true) { Add(checkForUpdatesButton = new SettingsButton { From 478f2dec9624f13c9fdf8503774094a19da2e9da Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 6 Oct 2020 22:39:35 +1030 Subject: [PATCH 077/326] Maintain the current gameplay state in OsuGame --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Input/ConfineMouseTracker.cs | 19 +++++++-------- osu.Game/Input/OsuConfineMouseMode.cs | 4 ++-- osu.Game/OsuGame.cs | 5 ++++ osu.Game/Screens/Play/Player.cs | 28 ++++++++++++---------- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 71cbdb345c..78179a781a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -70,7 +70,7 @@ namespace osu.Game.Configuration Set(OsuSetting.MouseDisableButtons, false); Set(OsuSetting.MouseDisableWheel, false); - Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.WhenOverlaysDisabled); + Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); // Graphics Set(OsuSetting.ShowFpsDisplay, false); diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 3b54c03bb0..c1089874ae 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -7,30 +7,29 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Configuration; -using osu.Game.Overlays; namespace osu.Game.Input { /// - /// Connects with , - /// while binding . - /// It is assumed that while overlay activation is , we should also confine the - /// mouse cursor if it has been requested with . + /// Connects with . + /// If is true, we should also confine the mouse cursor if it has been + /// requested with . /// public class ConfineMouseTracker : Component { private Bindable frameworkConfineMode; private Bindable osuConfineMode; - private IBindable overlayActivationMode; + private IBindable isGameplay; [BackgroundDependencyLoader] private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); + isGameplay = game.IsGameplay.GetBoundCopy(); + osuConfineMode.ValueChanged += _ => updateConfineMode(); - overlayActivationMode = game.OverlayActivationMode.GetBoundCopy(); - overlayActivationMode.BindValueChanged(_ => updateConfineMode(), true); + isGameplay.BindValueChanged(_ => updateConfineMode(), true); } private void updateConfineMode() @@ -45,8 +44,8 @@ namespace osu.Game.Input frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; break; - case OsuConfineMouseMode.WhenOverlaysDisabled: - frameworkConfineMode.Value = overlayActivationMode?.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; + case OsuConfineMouseMode.DuringGameplay: + frameworkConfineMode.Value = isGameplay.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs index e8411a3d9f..32b456395c 100644 --- a/osu.Game/Input/OsuConfineMouseMode.cs +++ b/osu.Game/Input/OsuConfineMouseMode.cs @@ -23,11 +23,11 @@ namespace osu.Game.Input Fullscreen, /// - /// The mouse cursor will be locked to the window bounds while overlays are disabled, + /// The mouse cursor will be locked to the window bounds during gameplay, /// but may otherwise move freely. /// [Description("During Gameplay")] - WhenOverlaysDisabled, + DuringGameplay, /// /// The mouse cursor will always be locked to the window bounds while the game has focus. diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 06e6e4bfb0..466ff13615 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -97,6 +97,11 @@ namespace osu.Game /// public readonly IBindable OverlayActivationMode = new Bindable(); + /// + /// Whether gameplay is currently active. + /// + public readonly Bindable IsGameplay = new BindableBool(); + protected OsuScreenStack ScreenStack; protected BackButton BackButton; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 175722c44e..a217d2a396 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play private readonly Bindable storyboardReplacesBackground = new Bindable(); + private readonly Bindable isGameplay = new Bindable(); + public int RestartCount; [Resolved] @@ -156,7 +158,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config) + private void load(AudioManager audio, OsuConfigManager config, OsuGame game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -172,6 +174,8 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + isGameplay.BindTo(game.IsGameplay); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); ScoreProcessor = ruleset.CreateScoreProcessor(); @@ -219,9 +223,9 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); - breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); + DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); + breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -353,14 +357,11 @@ namespace osu.Game.Screens.Play HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; } - private void updateOverlayActivationMode() + private void updateGameplayState() { - bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value; - - if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) - OverlayActivationMode.Value = OverlayActivation.UserTriggered; - else - OverlayActivationMode.Value = OverlayActivation.Disabled; + bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value; + OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; + isGameplay.Value = inGameplay; } private void updatePauseOnFocusLostState() => @@ -657,7 +658,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToTrack(musicController.CurrentTrack); - updateOverlayActivationMode(); + updateGameplayState(); } public override void OnSuspending(IScreen next) @@ -693,6 +694,9 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); + // Ensure we reset the IsGameplay state + isGameplay.Value = false; + fadeOut(); return base.OnExiting(next); } From b2dad67adead750b3b3f377d61449bf53e00d5a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 21:28:59 +0900 Subject: [PATCH 078/326] Fix unresolvable dependency in settings test scene --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index c216d1efe9..c0a525e012 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.General private SettingsButton checkForUpdatesButton; - [Resolved] + [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } [BackgroundDependencyLoader(true)] @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { if (!t.Result) { - notifications.Post(new SimpleNotification + notifications?.Post(new SimpleNotification { Text = $"You are running the latest release ({game.Version})", Icon = FontAwesome.Solid.CheckCircle, From 01636d501a0fc5dbf4a1959c23e9d3eaf577817f Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 18:36:15 +0300 Subject: [PATCH 079/326] 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 080/326] 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 081/326] 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 082/326] 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 083/326] 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 084/326] 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 085/326] 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 8847b88e653490a62d5319bc066702d9151d100c Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 11:44:41 +1030 Subject: [PATCH 086/326] Fix unit tests trying to resolve OsuGame --- osu.Game/Screens/Play/Player.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a217d2a396..932c5ba1df 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config, OsuGame game) + private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -174,7 +174,8 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - isGameplay.BindTo(game.IsGameplay); + if (game is OsuGame osuGame) + isGameplay.BindTo(osuGame.IsGameplay); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); From c8c5998af475a9860041eea732e61a10f949f069 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:02:35 +1030 Subject: [PATCH 087/326] Bail if FrameworkSetting.ConfineMouseMode is unavailable --- osu.Game/Input/ConfineMouseTracker.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index c1089874ae..3d16e44607 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -34,6 +34,10 @@ namespace osu.Game.Input private void updateConfineMode() { + // confine mode is unavailable on some platforms + if (frameworkConfineMode.Disabled) + return; + switch (osuConfineMode.Value) { case OsuConfineMouseMode.Never: From 7fff762dfc91222fe1823f3b73f90aa588b5129c Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:14:49 +1030 Subject: [PATCH 088/326] Rename IsGameplay --- osu.Game/Input/ConfineMouseTracker.cs | 10 +++++----- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 3d16e44607..3dadae6317 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -12,24 +12,24 @@ namespace osu.Game.Input { /// /// Connects with . - /// If is true, we should also confine the mouse cursor if it has been + /// If is true, we should also confine the mouse cursor if it has been /// requested with . /// public class ConfineMouseTracker : Component { private Bindable frameworkConfineMode; private Bindable osuConfineMode; - private IBindable isGameplay; + private IBindable localUserPlaying; [BackgroundDependencyLoader] private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); - isGameplay = game.IsGameplay.GetBoundCopy(); + localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); osuConfineMode.ValueChanged += _ => updateConfineMode(); - isGameplay.BindValueChanged(_ => updateConfineMode(), true); + localUserPlaying.BindValueChanged(_ => updateConfineMode(), true); } private void updateConfineMode() @@ -49,7 +49,7 @@ namespace osu.Game.Input break; case OsuConfineMouseMode.DuringGameplay: - frameworkConfineMode.Value = isGameplay.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never; + frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 466ff13615..c09ad0eeb9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -98,9 +98,9 @@ namespace osu.Game public readonly IBindable OverlayActivationMode = new Bindable(); /// - /// Whether gameplay is currently active. + /// Whether the local user is currently interacting with the game in a way that should not be interrupted. /// - public readonly Bindable IsGameplay = new BindableBool(); + public readonly Bindable LocalUserPlaying = new BindableBool(); protected OsuScreenStack ScreenStack; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 932c5ba1df..e6c15521af 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play private readonly Bindable storyboardReplacesBackground = new Bindable(); - private readonly Bindable isGameplay = new Bindable(); + private readonly Bindable localUserPlaying = new Bindable(); public int RestartCount; @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); if (game is OsuGame osuGame) - isGameplay.BindTo(osuGame.IsGameplay); + localUserPlaying.BindTo(osuGame.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); @@ -362,7 +362,7 @@ namespace osu.Game.Screens.Play { bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value; OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; - isGameplay.Value = inGameplay; + localUserPlaying.Value = inGameplay; } private void updatePauseOnFocusLostState() => @@ -695,8 +695,8 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); - // Ensure we reset the IsGameplay state - isGameplay.Value = false; + // Ensure we reset the LocalUserPlaying state + localUserPlaying.Value = false; fadeOut(); return base.OnExiting(next); From 485bd962c7d52d738547af4bcd43af535e2e935a Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:15:17 +1030 Subject: [PATCH 089/326] Also reset LocalUserPlaying in OnSuspending --- osu.Game/Screens/Play/Player.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e6c15521af..43dce0786d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -666,6 +666,9 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); + // Ensure we reset the LocalUserPlaying state + localUserPlaying.Value = false; + fadeOut(); base.OnSuspending(next); } From d1ec3806927a3bb042e47b76be921582ed87575e Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:15:32 +1030 Subject: [PATCH 090/326] Don't cache ConfineMouseTracker --- osu.Game/OsuGame.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c09ad0eeb9..d22ac1aec8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -549,7 +549,6 @@ namespace osu.Game BackButton.Receptor receptor; dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); - dependencies.Cache(confineMouseTracker = new ConfineMouseTracker()); AddRange(new Drawable[] { @@ -586,7 +585,7 @@ namespace osu.Game leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker, - confineMouseTracker + confineMouseTracker = new ConfineMouseTracker() }); ScreenStack.ScreenPushed += screenPushed; From 8b8eb00bd75c4790bec72bdd8a79f5e6ddddf457 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:16:58 +1030 Subject: [PATCH 091/326] Permit nulls rather than casting to OsuGame --- osu.Game/Screens/Play/Player.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 43dce0786d..39a6ac4ded 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -157,8 +157,8 @@ namespace osu.Game.Screens.Play DrawableRuleset.SetRecordTarget(recordingReplay = new Replay()); } - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game) + [BackgroundDependencyLoader(true)] + private void load(AudioManager audio, OsuConfigManager config, OsuGame game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -174,8 +174,8 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - if (game is OsuGame osuGame) - localUserPlaying.BindTo(osuGame.LocalUserPlaying); + if (game != null) + localUserPlaying.BindTo(game.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); From d6d0bd90a39e48747ce34ba08629373470127829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 15:34:03 +0900 Subject: [PATCH 092/326] Extract tuple into class --- osu.Game/Scoring/HitResultDisplayStatistic.cs | 41 +++++++++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 17 ++++---- 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Scoring/HitResultDisplayStatistic.cs diff --git a/osu.Game/Scoring/HitResultDisplayStatistic.cs b/osu.Game/Scoring/HitResultDisplayStatistic.cs new file mode 100644 index 0000000000..d43d8bf0ba --- /dev/null +++ b/osu.Game/Scoring/HitResultDisplayStatistic.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.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring +{ + /// + /// Compiled result data for a specific in a score. + /// + public class HitResultDisplayStatistic + { + /// + /// The associated result type. + /// + public HitResult Result { get; } + + /// + /// The count of successful hits of this type. + /// + public int Count { get; } + + /// + /// The maximum achievable hits of this type. May be null if undetermined. + /// + public int? MaxCount { get; } + + /// + /// A custom display name for the result type. May be provided by rulesets to give better clarity. + /// + public string DisplayName { get; } + + public HitResultDisplayStatistic(HitResult result, int count, int? maxCount, string displayName) + { + Result = result; + Count = count; + MaxCount = maxCount; + DisplayName = displayName; + } + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 0206989231..7cd9578ff1 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -213,22 +213,22 @@ namespace osu.Game.Scoring set => isLegacyScore = value; } - public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay() + public IEnumerable GetStatisticsForDisplay() { - foreach (var key in OrderAttributeUtils.GetValuesInOrder()) + foreach (var r in Ruleset.CreateInstance().GetHitResults()) { - if (key.IsBonus()) + if (r.result.IsBonus()) continue; - int value = Statistics.GetOrDefault(key); + int value = Statistics.GetOrDefault(r.result); - switch (key) + switch (r.result) { case HitResult.SmallTickHit: { int total = value + Statistics.GetOrDefault(HitResult.SmallTickMiss); if (total > 0) - yield return (key, value, total); + yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); break; } @@ -237,7 +237,7 @@ namespace osu.Game.Scoring { int total = value + Statistics.GetOrDefault(HitResult.LargeTickMiss); if (total > 0) - yield return (key, value, total); + yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); break; } @@ -247,8 +247,7 @@ namespace osu.Game.Scoring break; default: - if (value > 0 || key == HitResult.Miss) - yield return (key, value, null); + yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName); break; } From 3363c3399eaa8a5e0a4efbb2e1742d694639c232 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 15:34:23 +0900 Subject: [PATCH 093/326] Allow rulesets to specify valid HitResult types (and display names for them) --- osu.Game/Rulesets/Ruleset.cs | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 915544d010..48d94d2b3f 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,8 +23,10 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; using JetBrains.Annotations; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Utils; namespace osu.Game.Rulesets { @@ -220,5 +222,52 @@ namespace osu.Game.Rulesets /// The s to display. Each may contain 0 or more . [NotNull] public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); + + /// + /// Get all valid s for this ruleset. + /// Generally used for results display purposes, where it can't be determined if zero-count means the user has not achieved any or the type is not used by this ruleset. + /// + /// + /// All valid s along with a display-friendly name. + /// + public IEnumerable<(HitResult result, string displayName)> GetHitResults() + { + var validResults = GetValidHitResults(); + + // enumerate over ordered list to guarantee return order is stable. + foreach (var result in OrderAttributeUtils.GetValuesInOrder()) + { + switch (result) + { + // hard blocked types, should never be displayed even if the ruleset tells us to. + case HitResult.None: + case HitResult.IgnoreHit: + case HitResult.IgnoreMiss: + // display is handled as a completion count with corresponding "hit" type. + case HitResult.LargeTickMiss: + case HitResult.SmallTickMiss: + continue; + } + + if (result == HitResult.Miss || validResults.Contains(result)) + yield return (result, GetDisplayNameForHitResult(result)); + } + } + + /// + /// Get all valid s for this ruleset. + /// Generally used for results display purposes, where it can't be determined if zero-count means the user has not achieved any or the type is not used by this ruleset. + /// + /// + /// is implicitly included. Special types like are ignored even when specified. + /// + protected virtual IEnumerable GetValidHitResults() => OrderAttributeUtils.GetValuesInOrder(); + + /// + /// Get a display friendly name for the specified result type. + /// + /// The result type to get the name for. + /// The display name. + public virtual string GetDisplayNameForHitResult(HitResult result) => result.GetDescription(); } } From 6020ec9ca3e786d95640da84dfc573ef181c452a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 15:34:36 +0900 Subject: [PATCH 094/326] Add valid result types for all rulesets --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 29 ++++++++++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 25 ++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 35 +++++++++++++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 22 ++++++++++++++++ 4 files changed, 111 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index ca75a816f1..eb845cdea6 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -141,6 +141,35 @@ namespace osu.Game.Rulesets.Catch public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch }; + protected override IEnumerable GetValidHitResults() + { + return new[] + { + HitResult.Great, + + HitResult.LargeTickHit, + HitResult.SmallTickHit, + HitResult.LargeBonus, + }; + } + + public override string GetDisplayNameForHitResult(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return "large droplet"; + + case HitResult.SmallTickHit: + return "small droplet"; + + case HitResult.LargeBonus: + return "bananas"; + } + + return base.GetDisplayNameForHitResult(result); + } + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 71ac85dd1b..d2feeb03af 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -319,6 +319,31 @@ namespace osu.Game.Rulesets.Mania return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } + protected override IEnumerable GetValidHitResults() + { + return new[] + { + HitResult.Perfect, + HitResult.Great, + HitResult.Good, + HitResult.Ok, + HitResult.Meh, + + HitResult.LargeTickHit, + }; + } + + public override string GetDisplayNameForHitResult(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return "hold tick"; + } + + return base.GetDisplayNameForHitResult(result); + } + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7f4a0dcbbb..d946e7a113 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -191,6 +191,41 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + protected override IEnumerable GetValidHitResults() + { + return new[] + { + HitResult.Great, + HitResult.Ok, + HitResult.Meh, + + HitResult.LargeTickHit, + HitResult.SmallTickHit, + HitResult.SmallBonus, + HitResult.LargeBonus, + }; + } + + public override string GetDisplayNameForHitResult(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return "slider tick"; + + case HitResult.SmallTickHit: + return "slider end"; + + case HitResult.SmallBonus: + return "spinner spin"; + + case HitResult.LargeBonus: + return "spinner bonus"; + } + + return base.GetDisplayNameForHitResult(result); + } + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList(); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9d485e3f20..f4c94c9248 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -159,6 +159,28 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + protected override IEnumerable GetValidHitResults() + { + return new[] + { + HitResult.Great, + HitResult.Ok, + + HitResult.SmallTickHit, + }; + } + + public override string GetDisplayNameForHitResult(HitResult result) + { + switch (result) + { + case HitResult.SmallTickHit: + return "drum tick"; + } + + return base.GetDisplayNameForHitResult(result); + } + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList(); From e281d724b85feabb494169b6bd007e8e91621e1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 15:35:04 +0900 Subject: [PATCH 095/326] Consume display name logic --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 22 +++++++++++-------- .../Scores/TopScoreStatisticsSection.cs | 8 +++---- .../ContractedPanelMiddleContent.cs | 8 +++---- .../Expanded/ExpandedPanelMiddleContent.cs | 4 ++-- .../Expanded/Statistics/HitResultStatistic.cs | 8 +++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 968355c377..231d888a4e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores /// /// The statistics that appear in the table, in order of appearance. /// - private readonly List statisticResultTypes = new List(); + private readonly List<(HitResult result, string displayName)> statisticResultTypes = new List<(HitResult, string)>(); private bool showPerformancePoints; @@ -101,15 +101,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; // All statistics across all scores, unordered. - var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.result)).ToHashSet(); + var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.Result)).ToHashSet(); + + var ruleset = scores.First().Ruleset.CreateInstance(); foreach (var result in OrderAttributeUtils.GetValuesInOrder()) { if (!allScoreStatistics.Contains(result)) continue; - columns.Add(new TableColumn(result.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); - statisticResultTypes.Add(result); + string displayName = ruleset.GetDisplayNameForHitResult(result); + + columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + statisticResultTypes.Add((result, displayName)); } if (showPerformancePoints) @@ -163,18 +167,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } }; - var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.result); + var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.Result); foreach (var result in statisticResultTypes) { - if (!availableStatistics.TryGetValue(result, out var stat)) - stat = (result, 0, null); + if (!availableStatistics.TryGetValue(result.result, out var stat)) + stat = new HitResultDisplayStatistic(result.result, 0, null, result.displayName); content.Add(new OsuSpriteText { - Text = stat.maxCount == null ? $"{stat.count}" : $"{stat.count}/{stat.maxCount}", + Text = stat.MaxCount == null ? $"{stat.Count}" : $"{stat.Count}/{stat.MaxCount}", Font = OsuFont.GetFont(size: text_size), - Colour = stat.count == 0 ? Color4.Gray : Color4.White + Colour = stat.Count == 0 ? Color4.Gray : Color4.White }); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 05789e1fc0..93744dd6a3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -15,7 +14,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osuTK; @@ -117,7 +115,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; - statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(s => createStatisticsColumn(s.result, s.count, s.maxCount)); + statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); modsColumn.Mods = value.Mods; if (scoreManager != null) @@ -125,9 +123,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private TextColumn createStatisticsColumn(HitResult hitResult, int count, int? maxCount) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width) + private TextColumn createStatisticsColumn(HitResultDisplayStatistic stat) => new TextColumn(stat.DisplayName, smallFont, bottom_columns_min_width) { - Text = maxCount == null ? $"{count}" : $"{count}/{maxCount}" + Text = stat.MaxCount == null ? $"{stat.Count}" : $"{stat.Count}/{stat.MaxCount}" }; private class InfoColumn : CompositeDrawable diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 95ece1a9fb..9481f07342 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -13,7 +12,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -117,7 +115,7 @@ namespace osu.Game.Screens.Ranking.Contracted AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - ChildrenEnumerable = score.GetStatisticsForDisplay().Select(s => createStatistic(s.result, s.count, s.maxCount)) + ChildrenEnumerable = score.GetStatisticsForDisplay().Select(createStatistic) }, new FillFlowContainer { @@ -199,8 +197,8 @@ namespace osu.Game.Screens.Ranking.Contracted }; } - private Drawable createStatistic(HitResult result, int count, int? maxCount) - => createStatistic(result.GetDescription(), maxCount == null ? $"{count}" : $"{count}/{maxCount}"); + private Drawable createStatistic(HitResultDisplayStatistic result) + => createStatistic(result.DisplayName, result.MaxCount == null ? $"{result.Count}" : $"{result.Count}/{result.MaxCount}"); private Drawable createStatistic(string key, string value) => new Container { diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index ebab8c88f6..30b9f47f71 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.Ranking.Expanded var bottomStatistics = new List(); - foreach (var (key, value, maxCount) in score.GetStatisticsForDisplay()) - bottomStatistics.Add(new HitResultStatistic(key, value, maxCount)); + foreach (var result in score.GetStatisticsForDisplay()) + bottomStatistics.Add(new HitResultStatistic(result)); statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index a86033713f..31ef51a031 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { @@ -12,10 +12,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { private readonly HitResult result; - public HitResultStatistic(HitResult result, int count, int? maxCount = null) - : base(result.GetDescription(), count, maxCount) + public HitResultStatistic(HitResultDisplayStatistic result) + : base(result.DisplayName, result.Count, result.MaxCount) { - this.result = result; + this.result = result.Result; } [BackgroundDependencyLoader] From c0bc6a75b35cb73cf960c1c444125cbea6c1155f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 16:17:08 +0900 Subject: [PATCH 096/326] Show auxiliary judgements on next line --- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 14 ++++++++++++-- .../Expanded/Statistics/HitResultStatistic.cs | 6 +++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 30b9f47f71..b2d0c7a874 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Ranking.Expanded new CounterStatistic("pp", (int)(score.PP ?? 0)), }; - var bottomStatistics = new List(); + var bottomStatistics = new List(); foreach (var result in score.GetStatisticsForDisplay()) bottomStatistics.Add(new HitResultStatistic(result)); @@ -198,7 +198,17 @@ namespace osu.Game.Screens.Ranking.Expanded { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Content = new[] { bottomStatistics.Cast().ToArray() }, + Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }, + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index 31ef51a031..ada8dfabf0 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -10,18 +10,18 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { public class HitResultStatistic : CounterStatistic { - private readonly HitResult result; + public readonly HitResult Result; public HitResultStatistic(HitResultDisplayStatistic result) : base(result.DisplayName, result.Count, result.MaxCount) { - this.result = result.Result; + Result = result.Result; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - HeaderText.Colour = colours.ForHitResult(result); + HeaderText.Colour = colours.ForHitResult(Result); } } } From 6ac70945f2be7f3be6c6daabaf12a5bda8619cf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 16:17:28 +0900 Subject: [PATCH 097/326] Show bonus judgements on expanded panel --- osu.Game/Scoring/ScoreInfo.cs | 3 --- .../Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7cd9578ff1..596e98a6bd 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -217,9 +217,6 @@ namespace osu.Game.Scoring { foreach (var r in Ruleset.CreateInstance().GetHitResults()) { - if (r.result.IsBonus()) - continue; - int value = Statistics.GetOrDefault(r.result); switch (r.result) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 9481f07342..24f1116d0e 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -115,7 +116,7 @@ namespace osu.Game.Screens.Ranking.Contracted AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - ChildrenEnumerable = score.GetStatisticsForDisplay().Select(createStatistic) + ChildrenEnumerable = score.GetStatisticsForDisplay().Where(s => !s.Result.IsBonus()).Select(createStatistic) }, new FillFlowContainer { From 2e0a9f53c11a4d1e4fe2b63e22a3af3de579904d Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 17:52:39 +1030 Subject: [PATCH 098/326] Add test coverage --- .../Visual/Gameplay/TestSceneOverlayActivation.cs | 10 ++++++++++ osu.Game/Screens/Play/Player.cs | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index ce04b940e7..41f7582d31 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -23,32 +24,40 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestGameplayOverlayActivation() { + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); } [Test] public void TestGameplayOverlayActivationPaused() { + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("pause gameplay", () => Player.Pause()); + AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } [Test] public void TestGameplayOverlayActivationReplayLoaded() { + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true); + AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } [Test] public void TestGameplayOverlayActivationBreaks() { + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); + AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); } @@ -57,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected class OverlayTestPlayer : TestPlayer { public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value; + public new Bindable LocalUserPlaying => base.LocalUserPlaying; } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 39a6ac4ded..8830884a40 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play private readonly Bindable storyboardReplacesBackground = new Bindable(); - private readonly Bindable localUserPlaying = new Bindable(); + protected readonly Bindable LocalUserPlaying = new Bindable(); public int RestartCount; @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); if (game != null) - localUserPlaying.BindTo(game.LocalUserPlaying); + LocalUserPlaying.BindTo(game.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); @@ -362,7 +362,7 @@ namespace osu.Game.Screens.Play { bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value; OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; - localUserPlaying.Value = inGameplay; + LocalUserPlaying.Value = inGameplay; } private void updatePauseOnFocusLostState() => @@ -667,7 +667,7 @@ namespace osu.Game.Screens.Play screenSuspension?.Expire(); // Ensure we reset the LocalUserPlaying state - localUserPlaying.Value = false; + LocalUserPlaying.Value = false; fadeOut(); base.OnSuspending(next); @@ -699,7 +699,7 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); // Ensure we reset the LocalUserPlaying state - localUserPlaying.Value = false; + LocalUserPlaying.Value = false; fadeOut(); return base.OnExiting(next); From 67398b5d9551b7e147fa7398b337ee110ee456c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 16:30:14 +0900 Subject: [PATCH 099/326] Move timestamp text out of flow and attach to bottom edge --- .../Expanded/ExpandedPanelMiddleContent.cs | 265 +++++++++--------- 1 file changed, 134 insertions(+), 131 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index b2d0c7a874..5aac449adb 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -72,157 +72,160 @@ namespace osu.Game.Screens.Ranking.Expanded statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); - InternalChild = new FillFlowContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(20), - Children = new Drawable[] + new FillFlowContainer { - new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20), + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new FillFlowContainer { - new OsuSpriteText + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), - MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, - Truncate = true, - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), - Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), - MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, - Truncate = true, - }, - new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 40 }, - RelativeSizeAxes = Axes.X, - Height = 230, - Child = new AccuracyCircle(score) + new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - } - }, - scoreCounter = new TotalScoreCounter - { - Margin = new MarginPadding { Top = 0, Bottom = 5 }, - Current = { Value = 0 }, - Alpha = 0, - AlwaysPresent = true - }, - starAndModDisplay = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5, 0), - Children = new Drawable[] + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), + MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, + Truncate = true, + }, + new OsuSpriteText { - new StarRatingDisplay(beatmap) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }, - } - }, - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), + MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, + Truncate = true, + }, + new Container { - new OsuSpriteText + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 40 }, + RelativeSizeAxes = Axes.X, + Height = 230, + Child = new AccuracyCircle(score) { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = beatmap.Version, - Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), - }, - new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12)) + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + } + }, + scoreCounter = new TotalScoreCounter + { + Margin = new MarginPadding { Top = 0, Bottom = 5 }, + Current = { Value = 0 }, + Alpha = 0, + AlwaysPresent = true + }, + starAndModDisplay = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5, 0), + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - }.With(t => - { - if (!string.IsNullOrEmpty(creator)) + new StarRatingDisplay(beatmap) { - t.AddText("mapped by "); - t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); - } - }) - } - }, - } - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new Drawable[] + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + } + }, + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = beatmap.Version, + Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), + }, + new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }.With(t => + { + if (!string.IsNullOrEmpty(creator)) + { + t.AddText("mapped by "); + t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); + } + }) + } + }, + } + }, + new FillFlowContainer { - new GridContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] { topStatistics.Cast().ToArray() }, - RowDimensions = new[] + new GridContainer { - new Dimension(GridSizeMode.AutoSize), - } - }, - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() }, - RowDimensions = new[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { topStatistics.Cast().ToArray() }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }, + new GridContainer { - new Dimension(GridSizeMode.AutoSize), - } - }, - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() }, - RowDimensions = new[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }, + new GridContainer { - new Dimension(GridSizeMode.AutoSize), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } } } } - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}" } + }, + new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}" } }; From f88ba1734bd6cc687d86de8a2a02a87d386dbbb9 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 18:11:47 +1030 Subject: [PATCH 100/326] Remove ConfineMouseTracker field --- osu.Game/OsuGame.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d22ac1aec8..772f9ff145 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -90,8 +90,6 @@ namespace osu.Game private IdleTracker idleTracker; - private ConfineMouseTracker confineMouseTracker; - /// /// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen. /// @@ -585,7 +583,7 @@ namespace osu.Game leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker, - confineMouseTracker = new ConfineMouseTracker() + new ConfineMouseTracker() }); ScreenStack.ScreenPushed += screenPushed; From 31d347be5cfd24d63d3e39b78ecec916bab843c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 16:30:26 +0900 Subject: [PATCH 101/326] Make extended score panel taller to better fit all information --- osu.Game/Screens/Ranking/ScorePanel.cs | 7 ++++++- osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 1904da7094..8c8a547277 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Ranking /// /// Height of the panel when expanded. /// - private const float expanded_height = 560; + private const float expanded_height = 586; /// /// Height of the top layer when the panel is expanded. @@ -105,11 +105,16 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { + // ScorePanel doesn't include the top extruding area in its own size. + // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. + const float vertical_fudge = 20; + InternalChild = content = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(40), + Y = vertical_fudge, Children = new Drawable[] { topLayerContainer = new Container diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs index c8010d1c32..67533aaa24 100644 --- a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking From f77ad8cf3907327834db7d12c129511f70c5b819 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 17:03:34 +0900 Subject: [PATCH 102/326] Remove unused using --- osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs index 67533aaa24..c8010d1c32 100644 --- a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking From f90ac2e76c2bf20c88fafef834b3c9110156f660 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 18:50:02 +1030 Subject: [PATCH 103/326] Ensure we assert after the seek has completed --- osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 41f7582d31..4fa4c00981 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -54,11 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); - AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); + AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); - AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); } protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer(); From 0f6eb9d4cb7750c1890da24f26d5080f42e643ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 17:40:54 +0900 Subject: [PATCH 104/326] Ensure music playback is stopped when retrying by any means --- osu.Game/Screens/Play/Player.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 175722c44e..90a0eb0027 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -441,6 +441,10 @@ namespace osu.Game.Screens.Play /// public void Restart() { + // at the point of restarting the track should either already be paused or the volume should be zero. + // stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader. + musicController.Stop(); + sampleRestart?.Play(); RestartRequested?.Invoke(); From 6487f58e9ad11cc8f28592593e693057e3ecb38e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 17:52:35 +0900 Subject: [PATCH 105/326] Fix failing tests --- osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 19294d12fc..528689e67c 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -193,6 +193,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo { + Ruleset = new OsuRuleset().RulesetInfo, User = new User { Username = "osu!" }, Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }))); From 94a6e2856570bcb281d2073cd7eb6f716362b0bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 18:40:09 +0900 Subject: [PATCH 106/326] Add back second removed condition --- osu.Game/Updater/UpdateManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 30e28f0e95..f772c6d282 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -61,6 +61,9 @@ namespace osu.Game.Updater public async Task CheckForUpdateAsync() { + if (!CanCheckForUpdate) + return false; + Task waitTask; lock (updateTaskLock) From 74af7cc5036911b47238d9cde41691c0bcf5cf83 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 7 Oct 2020 17:00:00 +0300 Subject: [PATCH 107/326] 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 108/326] 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 109/326] 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 110/326] 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 111/326] 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 b1029a124ca6efd53a24a950d290c33f5efed148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Oct 2020 22:57:20 +0200 Subject: [PATCH 112/326] Move event subscription to LoadComplete Prevents attempting to read from the `colours` field before it is actually injected. --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 4121e1f7bb..c8982b819a 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -113,7 +113,6 @@ namespace osu.Game.Screens.Edit.Timing }; controlPoints = group.ControlPoints.GetBoundCopy(); - controlPoints.CollectionChanged += (_, __) => createChildren(); } [Resolved] @@ -125,6 +124,12 @@ namespace osu.Game.Screens.Edit.Timing createChildren(); } + protected override void LoadComplete() + { + base.LoadComplete(); + controlPoints.CollectionChanged += (_, __) => createChildren(); + } + private void createChildren() { fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null); From ac44f6f679504554485edb3877f76615f463f1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Oct 2020 23:10:28 +0200 Subject: [PATCH 113/326] Ensure control point group exists after move If the control point group moved was empty, it would not be created due to a lack of ControlPointInfo.Add() calls. --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index c77d48ef0a..d76b5e7406 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -111,7 +111,8 @@ namespace osu.Game.Screens.Edit.Timing foreach (var cp in currentGroupItems) Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp); - SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time); + // the control point might not necessarily exist yet, if currentGroupItems was empty. + SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time, true); changeHandler?.EndChange(); } From d9089ef93c7d6a052a86fa55f27129903d1fa649 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 12:52:52 +0900 Subject: [PATCH 114/326] Add missing bonus type for taiko ruleset --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index f4c94c9248..7f8289aa91 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -167,6 +167,8 @@ namespace osu.Game.Rulesets.Taiko HitResult.Ok, HitResult.SmallTickHit, + + HitResult.SmallBonus, }; } @@ -176,6 +178,9 @@ namespace osu.Game.Rulesets.Taiko { case HitResult.SmallTickHit: return "drum tick"; + + case HitResult.SmallBonus: + return "strong bonus"; } return base.GetDisplayNameForHitResult(result); From f70252d07bc4ebea18b4cdf0d1e4bd44477354c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 12:52:58 +0900 Subject: [PATCH 115/326] Match plurality --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index eb845cdea6..bda595e840 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Catch return "small droplet"; case HitResult.LargeBonus: - return "bananas"; + return "banana"; } return base.GetDisplayNameForHitResult(result); From ef092de9baaf3bde1e50abf5441fcefd847b6007 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 16:56:55 +0900 Subject: [PATCH 116/326] Add missing UpdateHitObject calls and move local to usages (not via bindables) --- .../Compose/Components/BlueprintContainer.cs | 4 ++ .../Timeline/TimelineHitObjectBlueprint.cs | 4 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 42 +++++++------------ 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 970e16d1c3..addb970e8a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -436,8 +436,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Apply the start time at the newly snapped-to position double offset = result.Time.Value - draggedObject.StartTime; + foreach (HitObject obj in SelectionHandler.SelectedHitObjects) + { obj.StartTime += offset; + Beatmap.UpdateHitObject(obj); + } } return true; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index f0757a3dda..6c3bcfae32 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -392,6 +392,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; repeatHitObject.RepeatCount = proposedCount; + beatmap.UpdateHitObject(hitObject); break; case IHasDuration endTimeHitObject: @@ -401,10 +402,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; endTimeHitObject.Duration = snappedTime - hitObject.StartTime; + beatmap.UpdateHitObject(hitObject); break; } - - beatmap.UpdateHitObject(hitObject); } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index fb75d91d16..d02841a95f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -89,8 +89,6 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; - private readonly HashSet pendingUpdates = new HashSet(); - private bool isBatchApplying; /// @@ -150,7 +148,16 @@ namespace osu.Game.Screens.Edit /// The to update. public void UpdateHitObject([NotNull] HitObject hitObject) { - pendingUpdates.Add(hitObject); + if (isBatchApplying) + batchPendingUpdates.Add(hitObject); + else + { + beatmapProcessor?.PreProcess(); + processHitObject(hitObject); + beatmapProcessor?.PostProcess(); + + HitObjectUpdated?.Invoke(hitObject); + } } /// @@ -220,6 +227,8 @@ namespace osu.Game.Screens.Edit private readonly List batchPendingDeletes = new List(); + private readonly HashSet batchPendingUpdates = new HashSet(); + /// /// Apply a batch of operations in one go, without performing Pre/Postprocessing each time. /// @@ -237,14 +246,17 @@ namespace osu.Game.Screens.Edit foreach (var h in batchPendingDeletes) processHitObject(h); foreach (var h in batchPendingInserts) processHitObject(h); + foreach (var h in batchPendingUpdates) processHitObject(h); beatmapProcessor?.PostProcess(); foreach (var h in batchPendingDeletes) HitObjectRemoved?.Invoke(h); foreach (var h in batchPendingInserts) HitObjectAdded?.Invoke(h); + foreach (var h in batchPendingUpdates) HitObjectUpdated?.Invoke(h); batchPendingDeletes.Clear(); batchPendingInserts.Clear(); + batchPendingUpdates.Clear(); isBatchApplying = false; } @@ -254,28 +266,6 @@ namespace osu.Game.Screens.Edit /// public void Clear() => RemoveRange(HitObjects.ToArray()); - protected override void Update() - { - base.Update(); - - // debounce updates as they are common and may come from input events, which can run needlessly many times per update frame. - if (pendingUpdates.Count > 0) - { - beatmapProcessor?.PreProcess(); - - foreach (var hitObject in pendingUpdates) - processHitObject(hitObject); - - beatmapProcessor?.PostProcess(); - - // explicitly needs to be fired after PostProcess - foreach (var hitObject in pendingUpdates) - HitObjectUpdated?.Invoke(hitObject); - - pendingUpdates.Clear(); - } - } - private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); private void trackStartTime(HitObject hitObject) @@ -322,7 +312,7 @@ namespace osu.Game.Screens.Edit public void UpdateBeatmap() { foreach (var h in HitObjects) - pendingUpdates.Add(h); + batchPendingUpdates.Add(h); } } } From ce04daf053e20ba3a6506f5bd887696d6d6761a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 16:57:04 +0900 Subject: [PATCH 117/326] Split transaction handling code out into base class --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 21 ++------- .../Edit/LegacyEditorBeatmapPatcher.cs | 25 ++++++----- .../Edit/TransactionalCommitComponent.cs | 45 +++++++++++++++++++ 3 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Screens/Edit/TransactionalCommitComponent.cs diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index b69e9c4c51..0c80a3e187 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit /// /// Tracks changes to the . /// - public class EditorChangeHandler : IEditorChangeHandler + public class EditorChangeHandler : TransactionalCommitComponent, IEditorChangeHandler { public readonly Bindable CanUndo = new Bindable(); public readonly Bindable CanRedo = new Bindable(); @@ -41,7 +41,6 @@ namespace osu.Game.Screens.Edit } private readonly EditorBeatmap editorBeatmap; - private int bulkChangesStarted; private bool isRestoring; public const int MAX_SAVED_STATES = 50; @@ -70,22 +69,8 @@ namespace osu.Game.Screens.Edit private void hitObjectUpdated(HitObject obj) => SaveState(); - public void BeginChange() => bulkChangesStarted++; - - public void EndChange() + protected override void UpdateState() { - if (bulkChangesStarted == 0) - throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}."); - - if (--bulkChangesStarted == 0) - SaveState(); - } - - public void SaveState() - { - if (bulkChangesStarted > 0) - return; - if (isRestoring) return; @@ -120,7 +105,7 @@ namespace osu.Game.Screens.Edit /// The direction to restore in. If less than 0, an older state will be used. If greater than 0, a newer state will be used. public void RestoreState(int direction) { - if (bulkChangesStarted > 0) + if (TransactionActive) return; if (savedStates.Count == 0) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index fb7d0dd826..72d3421755 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -68,19 +68,20 @@ namespace osu.Game.Screens.Edit toRemove.Sort(); toAdd.Sort(); - editorBeatmap.ApplyBatchChanges(eb => - { - // Apply the changes. - for (int i = toRemove.Count - 1; i >= 0; i--) - eb.RemoveAt(toRemove[i]); + editorBeatmap.BeginChange(); - if (toAdd.Count > 0) - { - IBeatmap newBeatmap = readBeatmap(newState); - foreach (var i in toAdd) - eb.Insert(i, newBeatmap.HitObjects[i]); - } - }); + // Apply the changes. + for (int i = toRemove.Count - 1; i >= 0; i--) + editorBeatmap.RemoveAt(toRemove[i]); + + if (toAdd.Count > 0) + { + IBeatmap newBeatmap = readBeatmap(newState); + foreach (var i in toAdd) + editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); + } + + editorBeatmap.EndChange(); } private string readString(byte[] state) => Encoding.UTF8.GetString(state); diff --git a/osu.Game/Screens/Edit/TransactionalCommitComponent.cs b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs new file mode 100644 index 0000000000..87a29a6237 --- /dev/null +++ b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Screens.Edit +{ + /// + /// A component that tracks a batch change, only applying after all active changes are completed. + /// + public abstract class TransactionalCommitComponent + { + public bool TransactionActive => bulkChangesStarted > 0; + + private int bulkChangesStarted; + + /// + /// Signal the beginning of a change. + /// + public void BeginChange() => bulkChangesStarted++; + + /// + /// Signal the end of a change. + /// + /// Throws if was not first called. + public void EndChange() + { + if (bulkChangesStarted == 0) + throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}."); + + if (--bulkChangesStarted == 0) + UpdateState(); + } + + public void SaveState() + { + if (bulkChangesStarted > 0) + return; + + UpdateState(); + } + + protected abstract void UpdateState(); + } +} From a9bca671d0e3ae2d4a14cf36e5ac541a78bbb63e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:17:52 +0900 Subject: [PATCH 118/326] Make component and add hooking events --- .../Edit/TransactionalCommitComponent.cs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/TransactionalCommitComponent.cs b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs index 87a29a6237..3d3539ee2f 100644 --- a/osu.Game/Screens/Edit/TransactionalCommitComponent.cs +++ b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs @@ -2,14 +2,30 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; namespace osu.Game.Screens.Edit { /// /// A component that tracks a batch change, only applying after all active changes are completed. /// - public abstract class TransactionalCommitComponent + public abstract class TransactionalCommitComponent : Component { + /// + /// Fires whenever a transaction begins. Will not fire on nested transactions. + /// + public event Action TransactionBegan; + + /// + /// Fires when the last transaction completes. + /// + public event Action TransactionEnded; + + /// + /// Fires when is called and results in a non-transactional state save. + /// + public event Action SaveStateTriggered; + public bool TransactionActive => bulkChangesStarted > 0; private int bulkChangesStarted; @@ -17,7 +33,11 @@ namespace osu.Game.Screens.Edit /// /// Signal the beginning of a change. /// - public void BeginChange() => bulkChangesStarted++; + public void BeginChange() + { + if (bulkChangesStarted++ == 0) + TransactionBegan?.Invoke(); + } /// /// Signal the end of a change. @@ -29,14 +49,22 @@ namespace osu.Game.Screens.Edit throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}."); if (--bulkChangesStarted == 0) + { UpdateState(); + TransactionEnded?.Invoke(); + } } + /// + /// Force an update of the state with no attached transaction. + /// This is a no-op if a transaction is already active. Should generally be used as a safety measure to ensure granular changes are not left outside a transaction. + /// public void SaveState() { if (bulkChangesStarted > 0) return; + SaveStateTriggered?.Invoke(); UpdateState(); } From 0781fbd44360daccc23d0fe585a98c4b91b5676d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:18:20 +0900 Subject: [PATCH 119/326] Make EditorBeatmap implement TransactionalCommitComponent --- osu.Game/Screens/Edit/EditorBeatmap.cs | 61 +++++++------------ .../Screens/Edit/Setup/DifficultySection.cs | 2 +- 2 files changed, 24 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index d02841a95f..f776908a0b 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -8,7 +8,6 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; @@ -18,7 +17,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Edit { - public class EditorBeatmap : Component, IBeatmap, IBeatSnapProvider + public class EditorBeatmap : TransactionalCommitComponent, IBeatmap, IBeatSnapProvider { /// /// Invoked when a is added to this . @@ -89,19 +88,16 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; - private bool isBatchApplying; - /// /// Adds a collection of s to this . /// /// The s to add. public void AddRange(IEnumerable hitObjects) { - ApplyBatchChanges(_ => - { - foreach (var h in hitObjects) - Add(h); - }); + BeginChange(); + foreach (var h in hitObjects) + Add(h); + EndChange(); } /// @@ -129,7 +125,7 @@ namespace osu.Game.Screens.Edit mutableHitObjects.Insert(index, hitObject); - if (isBatchApplying) + if (TransactionActive) batchPendingInserts.Add(hitObject); else { @@ -148,16 +144,8 @@ namespace osu.Game.Screens.Edit /// The to update. public void UpdateHitObject([NotNull] HitObject hitObject) { - if (isBatchApplying) - batchPendingUpdates.Add(hitObject); - else - { - beatmapProcessor?.PreProcess(); - processHitObject(hitObject); - beatmapProcessor?.PostProcess(); - - HitObjectUpdated?.Invoke(hitObject); - } + // updates are debounced regardless of whether a batch is active. + batchPendingUpdates.Add(hitObject); } /// @@ -182,11 +170,10 @@ namespace osu.Game.Screens.Edit /// The s to remove. public void RemoveRange(IEnumerable hitObjects) { - ApplyBatchChanges(_ => - { - foreach (var h in hitObjects) - Remove(h); - }); + BeginChange(); + foreach (var h in hitObjects) + Remove(h); + EndChange(); } /// @@ -210,7 +197,7 @@ namespace osu.Game.Screens.Edit bindable.UnbindAll(); startTimeBindables.Remove(hitObject); - if (isBatchApplying) + if (TransactionActive) batchPendingDeletes.Add(hitObject); else { @@ -229,18 +216,18 @@ namespace osu.Game.Screens.Edit private readonly HashSet batchPendingUpdates = new HashSet(); - /// - /// Apply a batch of operations in one go, without performing Pre/Postprocessing each time. - /// - /// The function which will apply the batch changes. - public void ApplyBatchChanges(Action applyFunction) + protected override void Update() { - if (isBatchApplying) - throw new InvalidOperationException("Attempting to perform a batch application from within an existing batch"); + base.Update(); - isBatchApplying = true; + if (batchPendingUpdates.Count > 0) + UpdateState(); + } - applyFunction(this); + protected override void UpdateState() + { + if (batchPendingUpdates.Count == 0 && batchPendingDeletes.Count == 0 && batchPendingInserts.Count == 0) + return; beatmapProcessor?.PreProcess(); @@ -257,8 +244,6 @@ namespace osu.Game.Screens.Edit batchPendingDeletes.Clear(); batchPendingInserts.Clear(); batchPendingUpdates.Clear(); - - isBatchApplying = false; } /// @@ -309,7 +294,7 @@ namespace osu.Game.Screens.Edit /// /// Update all hit objects with potentially changed difficulty or control point data. /// - public void UpdateBeatmap() + public void UpdateAllHitObjects() { foreach (var h in HitObjects) batchPendingUpdates.Add(h); diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 2d8031c3c8..aa1d57db31 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value; Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value; - editorBeatmap.UpdateBeatmap(); + editorBeatmap.UpdateAllHitObjects(); } } } From b2d93f799f8cc269f037baccf6ad7e903cbb5b00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:22:35 +0900 Subject: [PATCH 120/326] Hook ChangeHandler to transactional events rather than individual ones --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 0c80a3e187..62187aed24 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -53,9 +53,9 @@ namespace osu.Game.Screens.Edit { this.editorBeatmap = editorBeatmap; - editorBeatmap.HitObjectAdded += hitObjectAdded; - editorBeatmap.HitObjectRemoved += hitObjectRemoved; - editorBeatmap.HitObjectUpdated += hitObjectUpdated; + editorBeatmap.TransactionBegan += BeginChange; + editorBeatmap.TransactionEnded += EndChange; + editorBeatmap.SaveStateTriggered += SaveState; patcher = new LegacyEditorBeatmapPatcher(editorBeatmap); @@ -63,12 +63,6 @@ namespace osu.Game.Screens.Edit SaveState(); } - private void hitObjectAdded(HitObject obj) => SaveState(); - - private void hitObjectRemoved(HitObject obj) => SaveState(); - - private void hitObjectUpdated(HitObject obj) => SaveState(); - protected override void UpdateState() { if (isRestoring) From 7ffab38728014fffdeedbc9906258f693c26167a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:43:08 +0900 Subject: [PATCH 121/326] Add test coverage of TransactionalCommitComponent --- .../TransactionalCommitComponentTest.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs diff --git a/osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs b/osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs new file mode 100644 index 0000000000..4ce9115ec4 --- /dev/null +++ b/osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs @@ -0,0 +1,100 @@ +// 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 NUnit.Framework; +using osu.Game.Screens.Edit; + +namespace osu.Game.Tests.Editing +{ + [TestFixture] + public class TransactionalCommitComponentTest + { + private TestHandler handler; + + [SetUp] + public void SetUp() + { + handler = new TestHandler(); + } + + [Test] + public void TestCommitTransaction() + { + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + + handler.BeginChange(); + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + handler.EndChange(); + + Assert.That(handler.StateUpdateCount, Is.EqualTo(1)); + } + + [Test] + public void TestSaveOutsideOfTransactionTriggersUpdates() + { + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + + handler.SaveState(); + Assert.That(handler.StateUpdateCount, Is.EqualTo(1)); + + handler.SaveState(); + Assert.That(handler.StateUpdateCount, Is.EqualTo(2)); + } + + [Test] + public void TestEventsFire() + { + int transactionBegan = 0; + int transactionEnded = 0; + int stateSaved = 0; + + handler.TransactionBegan += () => transactionBegan++; + handler.TransactionEnded += () => transactionEnded++; + handler.SaveStateTriggered += () => stateSaved++; + + handler.BeginChange(); + Assert.That(transactionBegan, Is.EqualTo(1)); + + handler.EndChange(); + Assert.That(transactionEnded, Is.EqualTo(1)); + + Assert.That(stateSaved, Is.EqualTo(0)); + handler.SaveState(); + Assert.That(stateSaved, Is.EqualTo(1)); + } + + [Test] + public void TestSaveDuringTransactionDoesntTriggerUpdate() + { + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + + handler.BeginChange(); + + handler.SaveState(); + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + + handler.EndChange(); + + Assert.That(handler.StateUpdateCount, Is.EqualTo(1)); + } + + [Test] + public void TestEndWithoutBeginThrows() + { + handler.BeginChange(); + handler.EndChange(); + Assert.That(() => handler.EndChange(), Throws.TypeOf()); + } + + private class TestHandler : TransactionalCommitComponent + { + public int StateUpdateCount { get; private set; } + + protected override void UpdateState() + { + StateUpdateCount++; + } + } + } +} From 38babf3de587e86d83655f2c056d690ca4ab9a44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:43:27 +0900 Subject: [PATCH 122/326] Update usages of ChangeHandler to EditorBeatmap where relevant --- .../Edit/TaikoSelectionHandler.cs | 8 ++++---- .../Edit/Compose/Components/SelectionHandler.cs | 14 ++++++-------- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index d5dd758e10..97998c0f76 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Taiko.Edit { var hits = SelectedHitObjects.OfType(); - ChangeHandler.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in hits) { @@ -65,19 +65,19 @@ namespace osu.Game.Rulesets.Taiko.Edit } } - ChangeHandler.EndChange(); + EditorBeatmap.EndChange(); } public void SetRimState(bool state) { var hits = SelectedHitObjects.OfType(); - ChangeHandler.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in hits) h.Type = state ? HitType.Rim : HitType.Centre; - ChangeHandler.EndChange(); + EditorBeatmap.EndChange(); } protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7808d7a5bc..6a180c439b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -238,9 +238,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void deleteSelected() { - ChangeHandler?.BeginChange(); EditorBeatmap?.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); - ChangeHandler?.EndChange(); } #endregion @@ -307,7 +305,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { - ChangeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); foreach (var h in SelectedHitObjects) { @@ -318,7 +316,7 @@ namespace osu.Game.Screens.Edit.Compose.Components h.Samples.Add(new HitSampleInfo { Name = sampleName }); } - ChangeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } /// @@ -328,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { - ChangeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); foreach (var h in SelectedHitObjects) { @@ -340,7 +338,7 @@ namespace osu.Game.Screens.Edit.Compose.Components EditorBeatmap?.UpdateHitObject(h); } - ChangeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } /// @@ -349,12 +347,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { - ChangeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); foreach (var h in SelectedHitObjects) h.SamplesBindable.RemoveAll(s => s.Name == sampleName); - ChangeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } #endregion diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3c5cbf30e9..74f324364a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -509,14 +509,14 @@ namespace osu.Game.Screens.Edit foreach (var h in objects) h.StartTime += timeOffset; - changeHandler.BeginChange(); + editorBeatmap.BeginChange(); editorBeatmap.SelectedHitObjects.Clear(); editorBeatmap.AddRange(objects); editorBeatmap.SelectedHitObjects.AddRange(objects); - changeHandler.EndChange(); + editorBeatmap.EndChange(); } protected void Undo() => changeHandler.RestoreState(-1); From 1027b608ffb86df7c35afff6a9dd8a34124d4607 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:52:49 +0900 Subject: [PATCH 123/326] Copy list content before firing events to avoid pollution --- osu.Game/Screens/Edit/EditorBeatmap.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index f776908a0b..098d05471f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -237,13 +237,19 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); - foreach (var h in batchPendingDeletes) HitObjectRemoved?.Invoke(h); - foreach (var h in batchPendingInserts) HitObjectAdded?.Invoke(h); - foreach (var h in batchPendingUpdates) HitObjectUpdated?.Invoke(h); - + // callbacks may modify the lists so let's be safe about it + var deletes = batchPendingDeletes.ToArray(); batchPendingDeletes.Clear(); + + var inserts = batchPendingInserts.ToArray(); batchPendingInserts.Clear(); + + var updates = batchPendingUpdates.ToArray(); batchPendingUpdates.Clear(); + + foreach (var h in deletes) HitObjectRemoved?.Invoke(h); + foreach (var h in inserts) HitObjectAdded?.Invoke(h); + foreach (var h in updates) HitObjectUpdated?.Invoke(h); } /// From afed832b19e3e8dfe3d3bf94105e0b84d2073f76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:06:46 +0900 Subject: [PATCH 124/326] Tidy up EditorBeatmap slightly --- .../Sliders/SliderSelectionBlueprint.cs | 2 +- .../Edit/TaikoSelectionHandler.cs | 2 +- .../Compose/Components/BlueprintContainer.cs | 4 +-- .../Compose/Components/SelectionHandler.cs | 2 +- .../Timeline/TimelineHitObjectBlueprint.cs | 4 +-- osu.Game/Screens/Edit/EditorBeatmap.cs | 34 +++++++++---------- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 94862eb205..f260c5a8fa 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updatePath() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; - editorBeatmap?.UpdateHitObject(HitObject); + editorBeatmap?.Update(HitObject); } public override MenuItem[] ContextMenuItems => new MenuItem[] diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 97998c0f76..ee92936fc2 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Taiko.Edit if (h.IsStrong != state) { h.IsStrong = state; - EditorBeatmap.UpdateHitObject(h); + EditorBeatmap.Update(h); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index addb970e8a..c7f87ae08e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // handle positional change etc. foreach (var obj in selectedHitObjects) - Beatmap.UpdateHitObject(obj); + Beatmap.Update(obj); changeHandler?.EndChange(); isDraggingBlueprint = false; @@ -440,7 +440,7 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (HitObject obj in SelectionHandler.SelectedHitObjects) { obj.StartTime += offset; - Beatmap.UpdateHitObject(obj); + Beatmap.Update(obj); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6a180c439b..e8ab09df85 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -335,7 +335,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (comboInfo == null || comboInfo.NewCombo == state) continue; comboInfo.NewCombo = state; - EditorBeatmap?.UpdateHitObject(h); + EditorBeatmap?.Update(h); } EditorBeatmap?.EndChange(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 6c3bcfae32..975433d407 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -392,7 +392,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; repeatHitObject.RepeatCount = proposedCount; - beatmap.UpdateHitObject(hitObject); + beatmap.Update(hitObject); break; case IHasDuration endTimeHitObject: @@ -402,7 +402,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; endTimeHitObject.Duration = snappedTime - hitObject.StartTime; - beatmap.UpdateHitObject(hitObject); + beatmap.Update(hitObject); break; } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 098d05471f..3278f44e06 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -88,6 +88,12 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; + private readonly List batchPendingInserts = new List(); + + private readonly List batchPendingDeletes = new List(); + + private readonly HashSet batchPendingUpdates = new HashSet(); + /// /// Adds a collection of s to this . /// @@ -142,12 +148,21 @@ namespace osu.Game.Screens.Edit /// Updates a , invoking and re-processing the beatmap. /// /// The to update. - public void UpdateHitObject([NotNull] HitObject hitObject) + public void Update([NotNull] HitObject hitObject) { // updates are debounced regardless of whether a batch is active. batchPendingUpdates.Add(hitObject); } + /// + /// Update all hit objects with potentially changed difficulty or control point data. + /// + public void UpdateAllHitObjects() + { + foreach (var h in HitObjects) + batchPendingUpdates.Add(h); + } + /// /// Removes a from this . /// @@ -210,12 +225,6 @@ namespace osu.Game.Screens.Edit } } - private readonly List batchPendingInserts = new List(); - - private readonly List batchPendingDeletes = new List(); - - private readonly HashSet batchPendingUpdates = new HashSet(); - protected override void Update() { base.Update(); @@ -270,7 +279,7 @@ namespace osu.Game.Screens.Edit var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime); mutableHitObjects.Insert(insertionIndex + 1, hitObject); - UpdateHitObject(hitObject); + Update(hitObject); }; } @@ -296,14 +305,5 @@ namespace osu.Game.Screens.Edit public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; public int BeatDivisor => beatDivisor?.Value ?? 1; - - /// - /// Update all hit objects with potentially changed difficulty or control point data. - /// - public void UpdateAllHitObjects() - { - foreach (var h in HitObjects) - batchPendingUpdates.Add(h); - } } } From c9f069d7edd6add1e6625f0e28921acbd9ffa610 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:17:57 +0900 Subject: [PATCH 125/326] Fix taiko's HitObjectComposer not allowing movement o f selected hitobjects --- osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index d5dd758e10..ac14e6131a 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -89,6 +89,8 @@ namespace osu.Game.Rulesets.Taiko.Edit yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; } + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + protected override void UpdateTernaryStates() { base.UpdateTernaryStates(); From 0967db768ffb922e0d6e46fe71ada6bf94731533 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:25:40 +0900 Subject: [PATCH 126/326] Add xmldoc covering usage restrictions --- osu.Game/OsuGame.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 772f9ff145..e6f6d526cf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -98,7 +98,11 @@ namespace osu.Game /// /// Whether the local user is currently interacting with the game in a way that should not be interrupted. /// - public readonly Bindable LocalUserPlaying = new BindableBool(); + /// + /// This is exclusively managed by . If other components are mutating this state, a more + /// resilient method should be used to ensure correct state. + /// + public Bindable LocalUserPlaying = new BindableBool(); protected OsuScreenStack ScreenStack; From 43a575484ad1440a799aa41433840b47f5299f1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:25:43 +0900 Subject: [PATCH 127/326] Remove pointless comments --- osu.Game/Screens/Play/Player.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8830884a40..80dd8ae92c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -666,7 +666,6 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); - // Ensure we reset the LocalUserPlaying state LocalUserPlaying.Value = false; fadeOut(); @@ -698,7 +697,6 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); - // Ensure we reset the LocalUserPlaying state LocalUserPlaying.Value = false; fadeOut(); From dbdb25ccf756cf48ec3ecff87a81aec86f0f0224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:29:19 +0900 Subject: [PATCH 128/326] Move reset logic to OsuGame --- osu.Game/OsuGame.cs | 3 +++ osu.Game/Screens/Play/Player.cs | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e6f6d526cf..d315b213ab 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -957,6 +957,9 @@ namespace osu.Game break; } + // reset on screen change for sanity. + LocalUserPlaying.Value = false; + if (current is IOsuScreen currentOsuScreen) OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 80dd8ae92c..45f194fc29 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -666,8 +666,6 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); - LocalUserPlaying.Value = false; - fadeOut(); base.OnSuspending(next); } @@ -697,8 +695,6 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); - LocalUserPlaying.Value = false; - fadeOut(); return base.OnExiting(next); } From 3114174e098a898bf09a9946b374fd99fc90ff48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:41:03 +0900 Subject: [PATCH 129/326] Add missing non-transactional SaveState calls --- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3278f44e06..946c6905db 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -141,6 +141,7 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); HitObjectAdded?.Invoke(hitObject); + SaveState(); } } @@ -222,6 +223,7 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); HitObjectRemoved?.Invoke(hitObject); + SaveState(); } } From 4ccd751604fdc8ceb9561f66f0dd5b5d50d66db6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:42:53 +0900 Subject: [PATCH 130/326] Further simplify non-transactional change logic --- osu.Game/Screens/Edit/EditorBeatmap.cs | 30 ++++++-------------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 946c6905db..165d2ba278 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -131,18 +131,9 @@ namespace osu.Game.Screens.Edit mutableHitObjects.Insert(index, hitObject); - if (TransactionActive) - batchPendingInserts.Add(hitObject); - else - { - // must be run after any change to hitobject ordering - beatmapProcessor?.PreProcess(); - processHitObject(hitObject); - beatmapProcessor?.PostProcess(); - - HitObjectAdded?.Invoke(hitObject); - SaveState(); - } + BeginChange(); + batchPendingInserts.Add(hitObject); + EndChange(); } /// @@ -213,18 +204,9 @@ namespace osu.Game.Screens.Edit bindable.UnbindAll(); startTimeBindables.Remove(hitObject); - if (TransactionActive) - batchPendingDeletes.Add(hitObject); - else - { - // must be run after any change to hitobject ordering - beatmapProcessor?.PreProcess(); - processHitObject(hitObject); - beatmapProcessor?.PostProcess(); - - HitObjectRemoved?.Invoke(hitObject); - SaveState(); - } + BeginChange(); + batchPendingDeletes.Add(hitObject); + EndChange(); } protected override void Update() From 2d0275ba958e9de5fbb8c68635177026a86487e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 03:07:01 +0900 Subject: [PATCH 131/326] Fix first hitobject in osu! hidden mod not getting correct fade applied --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 15 +++++++++++---- osu.Game/Rulesets/Mods/ModHidden.cs | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 80e40af717..d354a8a726 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -42,7 +42,11 @@ namespace osu.Game.Rulesets.Osu.Mods private double lastSliderHeadFadeOutStartTime; private double lastSliderHeadFadeOutDuration; - protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) + protected override void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, true); + + protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, false); + + private void applyState(DrawableHitObject drawable, bool increaseVisibility) { if (!(drawable is DrawableOsuHitObject d)) return; @@ -86,9 +90,12 @@ namespace osu.Game.Rulesets.Osu.Mods lastSliderHeadFadeOutStartTime = fadeOutStartTime; } - // we don't want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) - circle.ApproachCircle.Hide(); + if (!increaseVisibility) + { + // we don't want to see the approach circle + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + circle.ApproachCircle.Hide(); + } // fade out immediately after fade in. using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index a1915b974c..d81b439f6a 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -38,7 +38,13 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) { if (IncreaseFirstObjectVisibility.Value) + { + var firstObject = drawables.FirstOrDefault(); + if (firstObject != null) + firstObject.ApplyCustomUpdateState += ApplyFirstObjectIncreaseVisibilityState; + drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1); + } foreach (var dho in drawables) dho.ApplyCustomUpdateState += ApplyHiddenState; @@ -65,6 +71,20 @@ namespace osu.Game.Rulesets.Mods } } + /// + /// Apply a special visibility state to the first object in a beatmap, if the user chooses to turn on the "increase first object visibility" setting. + /// + /// The hit object to apply the state change to. + /// The state of the hit object. + protected virtual void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + /// + /// Apply a hidden state to the provided object. + /// + /// The hit object to apply the state change to. + /// The state of the hit object. protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) { } From e7eda19b0723b8edd16d68989434e340c8e9992d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 03:31:01 +0900 Subject: [PATCH 132/326] Reset new combo button state after successful placement --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 9b3314e2ad..0336c74386 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -201,7 +201,12 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void AddBlueprintFor(HitObject hitObject) { refreshTool(); + base.AddBlueprintFor(hitObject); + + // on successful placement, the new combo button should be reset as this is the most common user interaction. + if (Beatmap.SelectedHitObjects.Count == 0) + NewCombo.Value = TernaryState.False; } private void createPlacement() From 5966205037867aefc6c0a4ed4ea5d0ecbe312b46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 04:31:33 +0900 Subject: [PATCH 133/326] Fix ternary button states not updating correctly after a paste operation --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7808d7a5bc..c5e88ade84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public int SelectedCount => selectedBlueprints.Count; - public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject); + public IEnumerable SelectedHitObjects => EditorBeatmap.SelectedHitObjects; private Drawable content; From a5b2c4195efb48684a69a44655a1165c1168563b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 04:41:45 +0900 Subject: [PATCH 134/326] Fix incorrect timing distribution display due to lack of rounding --- .../Ranking/TestSceneHitEventTimingDistributionGraph.cs | 6 ++++++ .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 144f8da2fa..9059fe34af 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -23,6 +23,12 @@ namespace osu.Game.Tests.Visual.Ranking createTest(CreateDistributedHitEvents()); } + [Test] + public void TestAroundCentre() + { + createTest(Enumerable.Range(-100, 100).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + } + [Test] public void TestZeroTimeOffset() { diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index aa2a83774e..980fc68788 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Ranking.Statistics foreach (var e in hitEvents) { - int binOffset = (int)(e.TimeOffset / binSize); + int binOffset = (int)Math.Round(e.TimeOffset / binSize); bins[timing_distribution_centre_bin_index + binOffset]++; } From ff5a1937f5f17cba7eadd82d3157a9499f6e06ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 05:04:03 +0900 Subject: [PATCH 135/326] Fix test logic and stabilise rounding direction --- .../Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs | 2 +- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 9059fe34af..4bc843096f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAroundCentre() { - createTest(Enumerable.Range(-100, 100).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); } [Test] diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 980fc68788..93885b6e02 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Ranking.Statistics foreach (var e in hitEvents) { - int binOffset = (int)Math.Round(e.TimeOffset / binSize); + int binOffset = (int)Math.Round(e.TimeOffset / binSize, MidpointRounding.AwayFromZero); bins[timing_distribution_centre_bin_index + binOffset]++; } From 85b33fffd028c2a5153c038a13f116c9f19c7e0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 05:14:44 +0900 Subject: [PATCH 136/326] Fix incorrect comments --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7808d7a5bc..8a80ddd17c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handles the selected s being rotated. /// /// The delta angle to apply to the selection. - /// Whether any s could be moved. + /// Whether any s could be rotated. public virtual bool HandleRotation(float angle) => false; /// @@ -149,14 +149,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The delta scale to apply, in playfield local coordinates. /// The point of reference where the scale is originating from. - /// Whether any s could be moved. + /// Whether any s could be scaled. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; /// - /// Handled the selected s being flipped. + /// Handles the selected s being flipped. /// /// The direction to flip - /// Whether any s could be moved. + /// Whether any s could be flipped. public virtual bool HandleFlip(Direction direction) => false; public bool OnPressed(PlatformAction action) From eacc7dca9a0d3815e7a4055f50b77c92505be911 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 06:31:59 +0900 Subject: [PATCH 137/326] Fix SliderPath not handling Clear correctly --- osu.Game/Rulesets/Objects/SliderPath.cs | 1 + .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d577e8fdda..3083fcfccb 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.Objects c.Changed += invalidate; break; + case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: foreach (var c in args.OldItems.Cast()) c.Changed -= invalidate; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 8a80ddd17c..3fe0ab4396 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -159,6 +159,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be flipped. public virtual bool HandleFlip(Direction direction) => false; + /// + /// Handles the selected s being reversed pattern-wise. + /// + /// Whether any s could be reversed. + public virtual bool HandleReverse() => false; + public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) From 825e10ec8c350bae42e546c86370fada762473d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 06:32:23 +0900 Subject: [PATCH 138/326] Add reverse handler button to selection box --- .../Edit/Compose/Components/SelectionBox.cs | 19 +++++++++++++++++++ .../Compose/Components/SelectionHandler.cs | 1 + 2 files changed, 20 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 64191e48e2..b753c45cca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -17,10 +17,28 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action OnRotation; public Action OnScale; public Action OnFlip; + public Action OnReverse; public Action OperationStarted; public Action OperationEnded; + private bool canReverse; + + /// + /// Whether pattern reversing support should be enabled. + /// + public bool CanReverse + { + get => canReverse; + set + { + if (canReverse == value) return; + + canReverse = value; + recreate(); + } + } + private bool canRotate; /// @@ -125,6 +143,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (CanScaleX && CanScaleY) addFullScaleComponents(); if (CanScaleY) addYScaleComponents(); if (CanRotate) addRotationComponents(); + if (CanReverse) addButton(FontAwesome.Solid.Backward, "Reverse pattern", () => OnReverse?.Invoke()); } private void addRotationComponents() diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 3fe0ab4396..b74095455c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -103,6 +103,7 @@ namespace osu.Game.Screens.Edit.Compose.Components OnRotation = angle => HandleRotation(angle), OnScale = (amount, anchor) => HandleScale(amount, anchor), OnFlip = direction => HandleFlip(direction), + OnReverse = () => HandleReverse(), }; /// From 2a790c76d5f9c668a842bbc72b0ddf54c076d28a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 06:32:33 +0900 Subject: [PATCH 139/326] Add reverse implementation for osu! --- .../Edit/OsuSelectionHandler.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7ae0730e39..762c4a04e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -25,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit SelectionBox.CanRotate = canOperate; SelectionBox.CanScaleX = canOperate; SelectionBox.CanScaleY = canOperate; + SelectionBox.CanReverse = canOperate; } protected override void OnOperationEnded() @@ -41,6 +43,54 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; + public override bool HandleReverse() + { + var hitObjects = selectedMovableObjects; + + double endTime = hitObjects.Max(h => h.GetEndTime()); + double startTime = hitObjects.Min(h => h.StartTime); + + bool moreThanOneObject = hitObjects.Length > 1; + + foreach (var h in hitObjects) + { + if (moreThanOneObject) + h.StartTime = endTime - (h.GetEndTime() - startTime); + + if (h is Slider slider) + { + var points = slider.Path.ControlPoints.ToArray(); + Vector2 endPos = points.Last().Position.Value; + + slider.Path.ControlPoints.Clear(); + + slider.Position += endPos; + + PathType? lastType = null; + + for (var i = 0; i < points.Length; i++) + { + var p = points[i]; + p.Position.Value -= endPos; + + // propagate types forwards to last null type + if (i == points.Length - 1) + p.Type.Value = lastType; + else if (p.Type.Value != null) + { + var newType = p.Type.Value; + p.Type.Value = lastType; + lastType = newType; + } + + slider.Path.ControlPoints.Insert(0, p); + } + } + } + + return true; + } + public override bool HandleFlip(Direction direction) { var hitObjects = selectedMovableObjects; From 6649cb220431e7b3807fc8ae2d98c40e4f9d3811 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 06:41:53 +0900 Subject: [PATCH 140/326] Fix incorrect first object logic --- osu.Game/Rulesets/Mods/ModHidden.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index d81b439f6a..ad01bf036c 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -39,11 +39,13 @@ namespace osu.Game.Rulesets.Mods { if (IncreaseFirstObjectVisibility.Value) { + drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)); + var firstObject = drawables.FirstOrDefault(); if (firstObject != null) firstObject.ApplyCustomUpdateState += ApplyFirstObjectIncreaseVisibilityState; - drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1); + drawables = drawables.Skip(1); } foreach (var dho in drawables) From c86b37f60d0f1c036ff4bdd2964ce7532d71bfe0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 13:11:24 +0900 Subject: [PATCH 141/326] Add check to ensure MusicController doesn't play a delete pending beatmap's track --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0764f34697..12caf98021 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -150,7 +150,7 @@ namespace osu.Game.Overlays { if (IsUserPaused) return; - if (CurrentTrack.IsDummyDevice) + if (CurrentTrack.IsDummyDevice || beatmap.Value.BeatmapSetInfo.DeletePending) { if (beatmap.Disabled) return; From 68039cff4089bf84e233a3c99ef1f24ffd40836d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 13:11:44 +0900 Subject: [PATCH 142/326] Set beatmap to sane default on exiting editor --- osu.Game/Screens/Edit/Editor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3c5cbf30e9..d6dbfef2bd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -469,10 +469,17 @@ namespace osu.Game.Screens.Edit private void confirmExit() { + // stop the track if playing to allow the parent screen to choose a suitable playback mode. + Beatmap.Value.Track.Stop(); + if (isNewBeatmap) { // confirming exit without save means we should delete the new beatmap completely. beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet); + + // in theory this shouldn't be required but due to EF core not sharing instance states 100% + // MusicController is unaware of the changed DeletePending state. + Beatmap.SetDefault(); } exitConfirmed = true; From 389ffe7da5b799e1b7e800e07d2ccce894e7cbb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 13:23:18 +0900 Subject: [PATCH 143/326] Hide bonus result types from score table for the time being --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 231d888a4e..324299ccba 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -110,6 +110,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (!allScoreStatistics.Contains(result)) continue; + // for the time being ignore bonus result types. + // this is not being sent from the API and will be empty in all cases. + if (result.IsBonus()) + continue; + string displayName = ruleset.GetDisplayNameForHitResult(result); columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); From 8be19fd82059e79f9141027b9e4e3cda086e32fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 13:26:09 +0900 Subject: [PATCH 144/326] Increase height of contracted score panel to fit mods again --- osu.Game/Screens/Ranking/ScorePanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 8c8a547277..ee97ee55eb 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Ranking /// /// Height of the panel when contracted. /// - private const float contracted_height = 355; + private const float contracted_height = 385; /// /// Width of the panel when expanded. From beec0e41930ee69aafe15c7303101cca09f3dcaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 14:03:13 +0900 Subject: [PATCH 145/326] Hide children of SelectionBlueprint when not selected --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 71256093d5..4abdbfc244 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -89,9 +89,23 @@ namespace osu.Game.Rulesets.Edit } } - protected virtual void OnDeselected() => Hide(); + protected virtual void OnDeselected() + { + // selection blueprints are AlwaysPresent while the related DrawableHitObject is visible + // set the body piece's alpha directly to avoid arbitrarily rendering frame buffers etc. of children. + foreach (var d in InternalChildren) + d.Hide(); - protected virtual void OnSelected() => Show(); + Hide(); + } + + protected virtual void OnSelected() + { + foreach (var d in InternalChildren) + d.Show(); + + Show(); + } // When not selected, input is only required for the blueprint itself to receive IsHovering protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; From 34d1439f8ef598cf155b2ef8fa21a003b009d112 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 14:04:26 +0900 Subject: [PATCH 146/326] Only update slider selection blueprints paths when visible --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 94862eb205..8fe4b8a9cf 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -66,13 +66,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders pathVersion = HitObject.Path.Version.GetBoundCopy(); pathVersion.BindValueChanged(_ => updatePath()); + + BodyPiece.UpdateFrom(HitObject); } protected override void Update() { base.Update(); - BodyPiece.UpdateFrom(HitObject); + if (IsSelected) + BodyPiece.UpdateFrom(HitObject); } private Vector2 rightClickPosition; From 6b9e94ae93370a686aa62f7efeca2aaa0f9b721b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 14:05:00 +0900 Subject: [PATCH 147/326] Avoid retaining slider selection blueprints FBO backing textures after deselection --- .../Sliders/Components/SliderBodyPiece.cs | 2 + .../Sliders/SliderSelectionBlueprint.cs | 39 ++++++++++++++----- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 9349ef7a18..5581ce4bfd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components OriginPosition = body.PathOffset; } + public void RecyclePath() => body.RecyclePath(); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 8fe4b8a9cf..67f8088dbb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -24,10 +24,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderSelectionBlueprint : OsuSelectionBlueprint { - protected readonly SliderBodyPiece BodyPiece; - protected readonly SliderCircleSelectionBlueprint HeadBlueprint; - protected readonly SliderCircleSelectionBlueprint TailBlueprint; - protected readonly PathControlPointVisualiser ControlPointVisualiser; + protected SliderBodyPiece BodyPiece; + protected SliderCircleSelectionBlueprint HeadBlueprint; + protected SliderCircleSelectionBlueprint TailBlueprint; + protected PathControlPointVisualiser ControlPointVisualiser; + + private readonly DrawableSlider slider; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -44,17 +46,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { - var sliderObject = (Slider)slider.HitObject; + this.slider = slider; + } + [BackgroundDependencyLoader] + private void load() + { InternalChildren = new Drawable[] { BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) - { - RemoveControlPointsRequested = removeControlPoints - } }; } @@ -78,6 +80,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } + protected override void OnSelected() + { + AddInternal(ControlPointVisualiser = new PathControlPointVisualiser((Slider)slider.HitObject, true) + { + RemoveControlPointsRequested = removeControlPoints + }); + + base.OnSelected(); + } + + protected override void OnDeselected() + { + base.OnDeselected(); + + // throw away frame buffers on deselection. + ControlPointVisualiser?.Expire(); + BodyPiece.RecyclePath(); + } + private Vector2 rightClickPosition; protected override bool OnMouseDown(MouseDownEvent e) From 9baf704942288a18b60011c5ab9dbee80ca14633 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 15:38:58 +0900 Subject: [PATCH 148/326] Add local pooling to TimelineTickDisplay --- .../Visualisations/PointVisualisation.cs | 13 ++- .../Timeline/TimelineTickDisplay.cs | 102 ++++++++++++------ 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 1ac960039e..ea093e6a4e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -1,9 +1,9 @@ // 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.Shapes; +using osuTK; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations { @@ -13,15 +13,20 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations public class PointVisualisation : Box { public PointVisualisation(double startTime) + : this() + { + X = (float)startTime; + } + + public PointVisualisation() { Origin = Anchor.TopCentre; + RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; + Width = 1; EdgeSmoothness = new Vector2(1, 0); - - RelativePositionAxes = Axes.X; - X = (float)startTime; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 36ee976bf7..745f3e393b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +13,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineTickDisplay : TimelinePart + public class TimelineTickDisplay : TimelinePart { [Resolved] private EditorBeatmap beatmap { get; set; } @@ -31,15 +32,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] - private void load() - { - beatDivisor.BindValueChanged(_ => createLines(), true); - } + [Resolved(canBeNull: true)] + private Timeline timeline { get; set; } - private void createLines() + protected override void Update() { - Clear(); + base.Update(); + + int drawableIndex = 0; + + double minVisibleTime = double.MinValue; + double maxVisibleTime = double.MaxValue; + + if (timeline != null) + { + minVisibleTime = ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X / DrawWidth * Content.RelativeChildSize.X; + maxVisibleTime = ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X; + } for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { @@ -50,41 +59,68 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) { - var indexInBeat = beat % beatDivisor.Value; - - if (indexInBeat == 0) + if (t >= minVisibleTime && t <= maxVisibleTime) { - Add(new PointVisualisation(t) - { - Colour = BindableBeatDivisor.GetColourFor(1, colours), - Origin = Anchor.TopCentre, - }); - } - else - { - var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); - var colour = BindableBeatDivisor.GetColourFor(divisor, colours); - var height = 0.1f - (float)divisor / BindableBeatDivisor.VALID_DIVISORS.Last() * 0.08f; + var indexInBeat = beat % beatDivisor.Value; - Add(new PointVisualisation(t) + if (indexInBeat == 0) { - Colour = colour, - Height = height, - Origin = Anchor.TopCentre, - }); + var downbeatPoint = getNextUsablePoint(); + downbeatPoint.X = (float)t; - Add(new PointVisualisation(t) + downbeatPoint.Colour = BindableBeatDivisor.GetColourFor(1, colours); + downbeatPoint.Anchor = Anchor.TopLeft; + downbeatPoint.Origin = Anchor.TopCentre; + downbeatPoint.Height = 1; + } + else { - Colour = colour, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomCentre, - Height = height, - }); + var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); + var colour = BindableBeatDivisor.GetColourFor(divisor, colours); + var height = 0.1f - (float)divisor / BindableBeatDivisor.VALID_DIVISORS.Last() * 0.08f; + + var topPoint = getNextUsablePoint(); + topPoint.X = (float)t; + topPoint.Colour = colour; + topPoint.Height = height; + topPoint.Anchor = Anchor.TopLeft; + topPoint.Origin = Anchor.TopCentre; + + var bottomPoint = getNextUsablePoint(); + bottomPoint.X = (float)t; + bottomPoint.Colour = colour; + bottomPoint.Anchor = Anchor.BottomLeft; + bottomPoint.Origin = Anchor.BottomCentre; + bottomPoint.Height = height; + } } beat++; } } + + int usedDrawables = drawableIndex; + + // save a few drawables beyond the currently used for edge cases. + while (drawableIndex < Math.Min(usedDrawables + 16, Count)) + Children[drawableIndex++].Hide(); + + // expire any excess + while (drawableIndex < Count) + Children[drawableIndex++].Expire(); + + Drawable getNextUsablePoint() + { + PointVisualisation point; + if (drawableIndex >= Count) + Add(point = new PointVisualisation()); + else + point = Children[drawableIndex++]; + + point.Show(); + + return point; + } } } } From 017a8ce496f74e19bf726ac9ab7a5fdf139aa679 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 15:57:31 +0900 Subject: [PATCH 149/326] Only recalculate when display actually changes --- .../Timeline/TimelineTickDisplay.cs | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 745f3e393b..76428d6fbc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -32,6 +33,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both; } + private readonly Cached tickCache = new Cached(); + + [BackgroundDependencyLoader] + private void load() + { + beatDivisor.BindValueChanged(_ => tickCache.Invalidate()); + } + + private (float min, float max) visibleRange = (float.MinValue, float.MaxValue); + [Resolved(canBeNull: true)] private Timeline timeline { get; set; } @@ -39,17 +50,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.Update(); - int drawableIndex = 0; - - double minVisibleTime = double.MinValue; - double maxVisibleTime = double.MaxValue; - if (timeline != null) { - minVisibleTime = ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X / DrawWidth * Content.RelativeChildSize.X; - maxVisibleTime = ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X; + var newRange = ( + ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X / DrawWidth * Content.RelativeChildSize.X, + ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X); + + if (visibleRange != newRange) + tickCache.Invalidate(); + + visibleRange = newRange; } + if (!tickCache.IsValid) + createTicks(); + } + + private void createTicks() + { + int drawableIndex = 0; + for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { var point = beatmap.ControlPointInfo.TimingPoints[i]; @@ -59,7 +79,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) { - if (t >= minVisibleTime && t <= maxVisibleTime) + if (t >= visibleRange.min && t <= visibleRange.max) { var indexInBeat = beat % beatDivisor.Value; @@ -109,6 +129,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline while (drawableIndex < Count) Children[drawableIndex++].Expire(); + tickCache.Validate(); + Drawable getNextUsablePoint() { PointVisualisation point; From 955836916b340c3400efac07e4e385fbb6b4b744 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 16:45:11 +0900 Subject: [PATCH 150/326] Fix timeline tick display test making two instances of the component --- osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs index e33040acdc..20e58c3d2a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Screens.Edit.Compose.Components; -using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; namespace osu.Game.Tests.Visual.Editing @@ -13,7 +12,7 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneTimelineTickDisplay : TimelineTestScene { - public override Drawable CreateTestComponent() => new TimelineTickDisplay(); + public override Drawable CreateTestComponent() => Empty(); // tick display is implicitly inside the timeline. [BackgroundDependencyLoader] private void load() From ceb1494c33285a0219f58111e3e10b1183509c55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 16:46:54 +0900 Subject: [PATCH 151/326] Only run regeneration when passing a new min/max tick boundary --- .../Timeline/TimelineTickDisplay.cs | 83 ++++++++++++------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 76428d6fbc..c6e435b6ae 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -41,8 +41,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatDivisor.BindValueChanged(_ => tickCache.Invalidate()); } + /// + /// The visible time/position range of the timeline. + /// private (float min, float max) visibleRange = (float.MinValue, float.MaxValue); + /// + /// The next time/position value to the left of the display when tick regeneration needs to be run. + /// + private float? nextMinTick; + + /// + /// The next time/position value to the right of the display when tick regeneration needs to be run. + /// + private float? nextMaxTick; + [Resolved(canBeNull: true)] private Timeline timeline { get; set; } @@ -57,9 +70,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X); if (visibleRange != newRange) - tickCache.Invalidate(); + { + visibleRange = newRange; - visibleRange = newRange; + // actual regeneration only needs to occur if we've passed one of the known next min/max tick boundaries. + if (nextMinTick == null || nextMaxTick == null || (visibleRange.min < nextMinTick || visibleRange.max > nextMaxTick)) + tickCache.Invalidate(); + } } if (!tickCache.IsValid) @@ -69,6 +86,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void createTicks() { int drawableIndex = 0; + int highestDivisor = BindableBeatDivisor.VALID_DIVISORS.Last(); + + nextMinTick = null; + nextMaxTick = null; for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { @@ -79,40 +100,39 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) { - if (t >= visibleRange.min && t <= visibleRange.max) + float xPos = (float)t; + + if (t < visibleRange.min) + nextMinTick = xPos; + else if (t > visibleRange.max) + nextMaxTick ??= xPos; + else { + // if this is the first beat in the beatmap, there is no next min tick + if (beat == 0 && i == 0) + nextMinTick = float.MinValue; + var indexInBeat = beat % beatDivisor.Value; - if (indexInBeat == 0) - { - var downbeatPoint = getNextUsablePoint(); - downbeatPoint.X = (float)t; + var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); + var colour = BindableBeatDivisor.GetColourFor(divisor, colours); - downbeatPoint.Colour = BindableBeatDivisor.GetColourFor(1, colours); - downbeatPoint.Anchor = Anchor.TopLeft; - downbeatPoint.Origin = Anchor.TopCentre; - downbeatPoint.Height = 1; - } - else - { - var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); - var colour = BindableBeatDivisor.GetColourFor(divisor, colours); - var height = 0.1f - (float)divisor / BindableBeatDivisor.VALID_DIVISORS.Last() * 0.08f; + // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. + var height = indexInBeat == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f; - var topPoint = getNextUsablePoint(); - topPoint.X = (float)t; - topPoint.Colour = colour; - topPoint.Height = height; - topPoint.Anchor = Anchor.TopLeft; - topPoint.Origin = Anchor.TopCentre; + var topPoint = getNextUsablePoint(); + topPoint.X = xPos; + topPoint.Colour = colour; + topPoint.Height = height; + topPoint.Anchor = Anchor.TopLeft; + topPoint.Origin = Anchor.TopCentre; - var bottomPoint = getNextUsablePoint(); - bottomPoint.X = (float)t; - bottomPoint.Colour = colour; - bottomPoint.Anchor = Anchor.BottomLeft; - bottomPoint.Origin = Anchor.BottomCentre; - bottomPoint.Height = height; - } + var bottomPoint = getNextUsablePoint(); + bottomPoint.X = xPos; + bottomPoint.Colour = colour; + bottomPoint.Anchor = Anchor.BottomLeft; + bottomPoint.Origin = Anchor.BottomCentre; + bottomPoint.Height = height; } beat++; @@ -137,8 +157,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (drawableIndex >= Count) Add(point = new PointVisualisation()); else - point = Children[drawableIndex++]; + point = Children[drawableIndex]; + drawableIndex++; point.Show(); return point; From 5d888f687ae809c1086ebce68033978475a16c56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 16:49:51 +0900 Subject: [PATCH 152/326] Account for the width of points so they don't suddenly appear at timeline edges --- .../Timelines/Summary/Visualisations/PointVisualisation.cs | 6 ++++-- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index ea093e6a4e..b0ecffdd24 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -12,6 +12,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// public class PointVisualisation : Box { + public const float WIDTH = 1; + public PointVisualisation(double startTime) : this() { @@ -25,8 +27,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Width = 1; - EdgeSmoothness = new Vector2(1, 0); + Width = WIDTH; + EdgeSmoothness = new Vector2(WIDTH, 0); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index c6e435b6ae..ce73a2b50b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (timeline != null) { var newRange = ( - ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X / DrawWidth * Content.RelativeChildSize.X, - ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X); + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X, + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X); if (visibleRange != newRange) { From a0af2eb6c880fc727d15fcfdb662914a89d32d37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 16:54:43 +0900 Subject: [PATCH 153/326] Private protect setters --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 67f8088dbb..9cfb02ab20 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -24,10 +24,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderSelectionBlueprint : OsuSelectionBlueprint { - protected SliderBodyPiece BodyPiece; - protected SliderCircleSelectionBlueprint HeadBlueprint; - protected SliderCircleSelectionBlueprint TailBlueprint; - protected PathControlPointVisualiser ControlPointVisualiser; + protected SliderBodyPiece BodyPiece { get; private set; } + protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; } + protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; } + protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } private readonly DrawableSlider slider; From 144726e3c691aa2c0259cdae6e03f7d05759c8e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 17:12:01 +0900 Subject: [PATCH 154/326] Better guard against taiko swells becoming strong --- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 ++ osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs | 14 +++++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index ed7b8589ba..607eaf5dbd 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => { TaikoHitObject first = x.First(); - if (x.Skip(1).Any() && !(first is Swell)) + if (x.Skip(1).Any() && first.CanBeStrong) first.IsStrong = true; return first; }).ToList(); diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index eeae6e79f8..bf8b7bc178 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Objects set => Duration = value - StartTime; } + public override bool CanBeStrong => false; + public double Duration { get; set; } /// diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 2922010001..d2c37d965c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.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.Threading; using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; @@ -30,6 +31,11 @@ namespace osu.Game.Rulesets.Taiko.Objects public readonly Bindable IsStrongBindable = new BindableBool(); + /// + /// Whether this can be made a "strong" (large) hit. + /// + public virtual bool CanBeStrong => true; + /// /// Whether this HitObject is a "strong" type. /// Strong hit objects give more points for hitting the hit object with both keys. @@ -37,7 +43,13 @@ namespace osu.Game.Rulesets.Taiko.Objects public bool IsStrong { get => IsStrongBindable.Value; - set => IsStrongBindable.Value = value; + set + { + if (value && !CanBeStrong) + throw new InvalidOperationException($"Object of type {GetType()} cannot be strong"); + + IsStrongBindable.Value = value; + } } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) From cb96a40dd6eb389668ff2cfec630f47ec027029a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 17:12:10 +0900 Subject: [PATCH 155/326] Fix bindable propagation potentially making swells strong --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 9cd23383c4..d8d75a7614 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -158,7 +158,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { base.LoadSamples(); - isStrong.Value = getStrongSamples().Any(); + if (HitObject.CanBeStrong) + isStrong.Value = getStrongSamples().Any(); } private void updateSamplesFromStrong() From 21c6242f9064b8a8ac51af371640df29eb876718 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:35:44 +0900 Subject: [PATCH 156/326] Fix bar lines ("down beat" as people call it) showing up too often in timeline --- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index ce73a2b50b..724256af8b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -112,13 +112,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (beat == 0 && i == 0) nextMinTick = float.MinValue; - var indexInBeat = beat % beatDivisor.Value; + var indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); var colour = BindableBeatDivisor.GetColourFor(divisor, colours); // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. - var height = indexInBeat == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f; + var height = indexInBar == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f; var topPoint = getNextUsablePoint(); topPoint.X = xPos; From febfe9cdd049e6bef918074c6406411f1db6884d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:43:16 +0900 Subject: [PATCH 157/326] Don't fade the approach circle on increased visibility --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index d354a8a726..f69cacd432 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -90,7 +90,14 @@ namespace osu.Game.Rulesets.Osu.Mods lastSliderHeadFadeOutStartTime = fadeOutStartTime; } - if (!increaseVisibility) + Drawable fadeTarget = circle; + + if (increaseVisibility) + { + // only fade the circle piece (not the approach circle) for the increased visibility object. + fadeTarget = circle.CirclePiece; + } + else { // we don't want to see the approach circle using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) @@ -99,8 +106,7 @@ namespace osu.Game.Rulesets.Osu.Mods // fade out immediately after fade in. using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) - circle.FadeOut(fadeOutDuration); - + fadeTarget.FadeOut(fadeOutDuration); break; case DrawableSlider slider: From edaf6db5c6890a8475dd31b4dbfab45d3ddd9fa6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:44:23 +0900 Subject: [PATCH 158/326] Reference EditorBeatmap directly for selected objects --- .../Edit/Compose/Components/SelectionHandler.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c5e88ade84..8902e8119d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Edit.Compose.Components public int SelectedCount => selectedBlueprints.Count; - public IEnumerable SelectedHitObjects => EditorBeatmap.SelectedHitObjects; - private Drawable content; private OsuSpriteText selectionDetailsText; @@ -309,7 +307,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { ChangeHandler?.BeginChange(); - foreach (var h in SelectedHitObjects) + foreach (var h in EditorBeatmap.SelectedHitObjects) { // Make sure there isn't already an existing sample if (h.Samples.Any(s => s.Name == sampleName)) @@ -330,7 +328,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { ChangeHandler?.BeginChange(); - foreach (var h in SelectedHitObjects) + foreach (var h in EditorBeatmap.SelectedHitObjects) { var comboInfo = h as IHasComboInformation; @@ -351,7 +349,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { ChangeHandler?.BeginChange(); - foreach (var h in SelectedHitObjects) + foreach (var h in EditorBeatmap.SelectedHitObjects) h.SamplesBindable.RemoveAll(s => s.Name == sampleName); ChangeHandler?.EndChange(); @@ -425,11 +423,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void UpdateTernaryStates() { - SelectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.NewCombo); + SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.NewCombo); foreach (var (sampleName, bindable) in SelectionSampleStates) { - bindable.Value = GetStateFromSelection(SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); + bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); } } From 3838f405dd6ac1d00ddfad5c86c7d1619982c517 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:50:05 +0900 Subject: [PATCH 159/326] Fix missed usages --- .../Edit/ManiaSelectionHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 12 ++++++------ .../Edit/TaikoSelectionHandler.cs | 8 ++++---- .../Edit/Compose/Components/BlueprintContainer.cs | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 65f40d7d0a..50629f41a9 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit int minColumn = int.MaxValue; int maxColumn = int.MinValue; - foreach (var obj in SelectedHitObjects.OfType()) + foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType()) { if (obj.Column < minColumn) minColumn = obj.Column; @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn); - foreach (var obj in SelectedHitObjects.OfType()) + foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType()) obj.Column += columnDelta; } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7ae0730e39..68c4869a45 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.OnSelectionChanged(); - bool canOperate = SelectedHitObjects.Count() > 1 || SelectedHitObjects.Any(s => s is Slider); + bool canOperate = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); SelectionBox.CanRotate = canOperate; SelectionBox.CanScaleX = canOperate; @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// The points to calculate a quad for. private Quad getSurroundingQuad(IEnumerable points) { - if (!SelectedHitObjects.Any()) + if (!EditorBeatmap.SelectedHitObjects.Any()) return new Quad(); Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); @@ -203,10 +203,10 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// All osu! hitobjects which can be moved/rotated/scaled. /// - private OsuHitObject[] selectedMovableObjects => SelectedHitObjects - .OfType() - .Where(h => !(h is Spinner)) - .ToArray(); + private OsuHitObject[] selectedMovableObjects => EditorBeatmap.SelectedHitObjects + .OfType() + .Where(h => !(h is Spinner)) + .ToArray(); /// /// Rotate a point around an arbitrary origin. diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index d5dd758e10..6e940be54d 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetStrongState(bool state) { - var hits = SelectedHitObjects.OfType(); + var hits = EditorBeatmap.SelectedHitObjects.OfType(); ChangeHandler.BeginChange(); @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetRimState(bool state) { - var hits = SelectedHitObjects.OfType(); + var hits = EditorBeatmap.SelectedHitObjects.OfType(); ChangeHandler.BeginChange(); @@ -93,8 +93,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { base.UpdateTernaryStates(); - selectionRimState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.Type == HitType.Rim); - selectionStrongState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.IsStrong); + selectionRimState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.Type == HitType.Rim); + selectionStrongState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.IsStrong); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 970e16d1c3..1eff716b3d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -436,7 +436,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Apply the start time at the newly snapped-to position double offset = result.Time.Value - draggedObject.StartTime; - foreach (HitObject obj in SelectionHandler.SelectedHitObjects) + foreach (HitObject obj in Beatmap.SelectedHitObjects) obj.StartTime += offset; } From 021777145fd279b0a2b80c9ba7e8cc9992b7729b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:51:52 +0900 Subject: [PATCH 160/326] 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 d7817cf4cf..3df894fbcc 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 fa2135580d..8b10f0a7f7 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 20a51e5feb..88abbca73d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From e618b62ccd2367a67b06b414bf47d487f9d71fca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 19:02:53 +0900 Subject: [PATCH 161/326] Update waveform tests --- osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs b/osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs index 0c1296b82c..c3a5a0e944 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneWaveform.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.Threading; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -76,7 +77,7 @@ namespace osu.Game.Tests.Visual.Editing }; }); - AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + AddUntilStep("wait for load", () => graph.Loaded.IsSet); } [Test] @@ -98,12 +99,18 @@ namespace osu.Game.Tests.Visual.Editing }; }); - AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + AddUntilStep("wait for load", () => graph.Loaded.IsSet); } public class TestWaveformGraph : WaveformGraph { - public new Waveform ResampledWaveform => base.ResampledWaveform; + public readonly ManualResetEventSlim Loaded = new ManualResetEventSlim(); + + protected override void OnWaveformRegenerated(Waveform waveform) + { + base.OnWaveformRegenerated(waveform); + Loaded.Set(); + } } } } From 573336cb47d2549a670a90c8ce1200411f89e376 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 20:12:17 +0900 Subject: [PATCH 162/326] Ensure stable sorting order in beatmap conversion tests --- .../ManiaBeatmapConversionTest.cs | 18 +++++++++++++++++- .../Tests/Beatmaps/BeatmapConversionTest.cs | 13 +++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 0c57267970..3d4bc4748b 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -83,11 +83,17 @@ namespace osu.Game.Rulesets.Mania.Tests RandomZ = snapshot.RandomZ; } + public override void PostProcess() + { + base.PostProcess(); + Objects.Sort(); + } + public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ; public override bool Equals(ConvertMapping other) => base.Equals(other) && Equals(other as ManiaConvertMapping); } - public struct ConvertValue : IEquatable + public struct ConvertValue : IEquatable, IComparable { /// /// A sane value to account for osu!stable using ints everwhere. @@ -102,5 +108,15 @@ namespace osu.Game.Rulesets.Mania.Tests => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) && Column == other.Column; + + public int CompareTo(ConvertValue other) + { + var result = StartTime.CompareTo(other.StartTime); + + if (result != 0) + return result; + + return Column.CompareTo(other.Column); + } } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index e492069c5e..fcf20a2eb2 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -34,6 +34,12 @@ namespace osu.Game.Tests.Beatmaps var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray()); var expectedResult = read(name); + foreach (var m in ourResult.Mappings) + m.PostProcess(); + + foreach (var m in expectedResult.Mappings) + m.PostProcess(); + Assert.Multiple(() => { int mappingCounter = 0; @@ -239,6 +245,13 @@ namespace osu.Game.Tests.Beatmaps set => Objects = value; } + /// + /// Invoked after this is populated to post-process the contained data. + /// + public virtual void PostProcess() + { + } + public virtual bool Equals(ConvertMapping other) => StartTime == other?.StartTime; } } From 696e3d53afdc3754c7d8a2565d022520664d1c4c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 20:50:09 +0900 Subject: [PATCH 163/326] 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 164/326] 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 165/326] 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 166/326] 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 167/326] 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 168/326] 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 169/326] 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 170/326] 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 b0f8a7794a58a573a839e237d103bc4d50ac3eaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 21:44:10 +0900 Subject: [PATCH 171/326] Make SelectionHandler require EditorBeatmap presence --- .../Compose/Components/SelectionHandler.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6e144fd5ff..4caceedc5a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected SelectionBox SelectionBox { get; private set; } - [Resolved(CanBeNull = true)] + [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } [Resolved(CanBeNull = true)] @@ -243,7 +243,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void deleteSelected() { - EditorBeatmap?.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); + EditorBeatmap.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); } #endregion @@ -310,7 +310,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { - EditorBeatmap?.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in EditorBeatmap.SelectedHitObjects) { @@ -321,7 +321,7 @@ namespace osu.Game.Screens.Edit.Compose.Components h.Samples.Add(new HitSampleInfo { Name = sampleName }); } - EditorBeatmap?.EndChange(); + EditorBeatmap.EndChange(); } /// @@ -331,7 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { - EditorBeatmap?.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in EditorBeatmap.SelectedHitObjects) { @@ -340,10 +340,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (comboInfo == null || comboInfo.NewCombo == state) continue; comboInfo.NewCombo = state; - EditorBeatmap?.Update(h); + EditorBeatmap.Update(h); } - EditorBeatmap?.EndChange(); + EditorBeatmap.EndChange(); } /// @@ -352,12 +352,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { - EditorBeatmap?.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in EditorBeatmap.SelectedHitObjects) h.SamplesBindable.RemoveAll(s => s.Name == sampleName); - EditorBeatmap?.EndChange(); + EditorBeatmap.EndChange(); } #endregion From 5017c92fe84973d58f3fa6c4c90af0b0858c2591 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:47:34 +0900 Subject: [PATCH 172/326] 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 173/326] 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 174/326] 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 175/326] 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 176/326] 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 177/326] 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 178/326] 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 179/326] 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 180/326] 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 181/326] 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 182/326] 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 183/326] 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 184/326] 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 185/326] 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 186/326] 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 187/326] 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 188/326] 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 189/326] 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 190/326] 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 191/326] 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 192/326] 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 193/326] 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 194/326] 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 195/326] 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 196/326] 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 197/326] 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 198/326] 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 199/326] 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 200/326] 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 201/326] 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 202/326] 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 203/326] 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 204/326] 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 205/326] 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 663b8069746a5b79c1acc0b276a31be0e8710825 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 13 Oct 2020 17:45:40 +0200 Subject: [PATCH 206/326] 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 207/326] 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 208/326] 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 24eff8c66d8aab85a6509f9ea095c13cd5e8a09e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 15:13:49 +0900 Subject: [PATCH 209/326] 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 210/326] 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 211/326] 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 212/326] 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 213/326] 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 214/326] 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 215/326] 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 216/326] 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 217/326] 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 218/326] 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 219/326] 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 220/326] 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 221/326] 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 222/326] 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 223/326] 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 224/326] 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 225/326] 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 226/326] 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 227/326] 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 228/326] 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 229/326] 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 230/326] 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 231/326] 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 232/326] 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 233/326] 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 234/326] 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 235/326] 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 236/326] 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 237/326] 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 238/326] 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 239/326] 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 240/326] 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 241/326] 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 242/326] 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 243/326] 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 244/326] 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 245/326] 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 246/326] 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 247/326] 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 248/326] 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 249/326] 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 250/326] 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 251/326] 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 252/326] 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 253/326] 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 254/326] 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 255/326] 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 256/326] 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 257/326] 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 258/326] 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 259/326] 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 260/326] 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 261/326] 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 262/326] 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 263/326] 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 264/326] 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 265/326] 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 266/326] 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 267/326] 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 268/326] 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 269/326] 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 270/326] 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 271/326] 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 272/326] 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 273/326] 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 274/326] 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 275/326] 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 276/326] 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 277/326] 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 278/326] 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 zcmV-+2Z{KJP)me#3{3O=paJEob5+V*U3o#8b12GSgK*5+fg)$R>*bGqz zu@xdrBK?##CxxX+6pDEdF%0n+{eGpa3S}h-QPbGi*m~i@h3~@Qu=2U#PN&nXr>AG~ z?AfzF4Gs?WVHv5MDf*dhf%q2U`!i?Gyq%ky)7Rq2WHS2v{JehU%9S~|_74#45DkQs zEDNRF5RFDRdOV)B@hP~p-|uI;ckiwsd^VB})Fa8ls4RmUQ6x!PI}-(QWo2cowY9Yo z-mW7@aPy35!VuQhdWymdZQHgjM4~+0qGS?;!*q6NXlPnh)io97a=F;{?b`zeQ_reP zk%xR38ykB&Jw3ezz+{@HF?{8L@5KX4j$evlEI+NgsIF$=g9QxqwlDP_-4Ub zMdOr-6lKrLM+b>nab!W5%ri%?cs>sOTKcX{ZEbCJSZb>y9*?ujmoHDikAKtDv_K`* z6}n)GUX#R|BD6%hAW9d6Ndl}gji?PIFj7%bp<*emd=Z@=m}U`f-AOg3LT=%vP}(<7KQM z$B#g^HduL=0DZp!dezKBWv9HWrluykW5*83%DV{qIIW64SV3QuFrkdRD+!-CaiSi3 zcq`~*u^8*_?tc9#=!?x*&h&$21XJ}{^A@mK1?XeKhK)1A%=)gQM~}8x zc^5)IFfhO#KYsi|Xm3_+1`Ev??Bzk|a6%1Z9dZWRt~Y z%dW6E@ut#6`2$w|S%Um3MmLejcV5g(7=AH$Sz5Sz{-DCmK0fu$kho|I~@SmOii z#9)VD4g&!vF(sT0Ul8oyEC&hZ@&LK-uM6G(C%(?n4mUY zKKR_s0d62nK3%43kZix8&-A@tj`_cBvQ7cXd4LEYQs4!`g|KEK*$wo_ zXSeBhujwJ~ioE0K(W5~RMvfervtYr3@sB?GXu_mPlU(89;hNLw z)IeDC`~6x^Pmcz|T17=g@40j5+VQ>i$dMyg@ZWl%F5t5PXp#qL6Vg3mX@5vJz{zeQ zIs`}tMgrNu_=19hIcwLhO^l6=)gmGyw6L%+&EdG+gv*9asB=G->=`jdv{rLbMr-<@+EK;xCYb;X}VHRAYp^7 z_-U97JuP}4@W8~06X(A7-g^&?8a2v6YX(|!up2E;;vF3w+Rd9cwW6Y;j>5vilUJ@> z`8N)q6XKQPBCU527R$PXD2M&Kz{{}c-|+PPxUfEu4J34)3v20hb#(?>T3U2iQ}6HZ zzvFzkijJVvOG`_2xY!Ncvk3SJun-s{@xpB5Bu7G3p>Rn{_i*TeM=(??o_OMk@aX7h zTZ__CZEbB@Wo4zdXU`t(+O=z4@J&9U6C)EnZQ3;Vym|99`nt#Cx#L>&Z(8}|k3a5S zvu4ewjg5^*u=@;fS$w$-M0?HAA0tD*2>8uYPd)V&EfdTlYJ)H8?d|P40!Cl7Xc2?r zIIs)Y3Ty%X0Bi>i=jP@%W0VX_@_&IF?*qd6&Ye4Zd_La}{HK5h1JWgZ8rD^bpGiMO z2v6CvWlLUGR+hsqm>4ko_wU!9dFGkQS>{(C0>Z-~vDz3A45p7h`lw9O%|*$R zDn~LPls332)v8L)xw3cf-Uhmm)#mkjwG}H?L`$wuP-N-1iY%*EB+6;h(4j+(C$!pF zl@KhNSdS!b2+1x+tr8{t1p1^R(f1ljyriV0s=K?}^7_1YWMrg<5zZ6?MhIDll7J)} z4j+sSRiQ}s{Q2`8VzFka>)oQDf^AAV_DHaFvZ~n%7H~kY#R^f6@G~rXoAM7;rRWBi zE?sI9$=4+$`a;M&b$!D)q3%I(l$@L#DQQ&+f5Wo6>FN7IJ#}COwL2(f+$}|iR*Ftf zPfrLndgM@3Q`0E|-LLNB6rvo)jzfnIRdASD!|i-3B_$OwKiU>YuoOn; zrzpXBNqG7Wxsb!u2E~dsQ{-W&peCbwO@}V_4Ie(-QWMjvAgR6b$}4Aaeab-!^%}8o zzsnS@Mj#+zd(f7oKq>h+Tu!I+jylep<>cfzjvYIeit_0c-yI?(lcoA(nTS5Jef#!g z=wi*vy%;B~j5x3Tt+(F#1iMP5kTi%Z=&m5C#f{XY!>XB+Cr|eB;(>%|@gYNoXpcYs zco@`D#*G^{lJM!1hB59s2vj~OwguGnWC{3 zVnwnWdRi1cnt#=*RR@}ynhuJ3yCl`I9UOa%>>{0l8p>oLx>EG$mkSpz)Y&(&6R^{^ zRASV()TR97Z#(72W9%YoYisq_UVBZ4fT8IAHLy$?fTX}q!74lZRiS|J&@wYKwN0Bg zSz3pFOv?wezFmibjzU|vZk>kwepQD3s&um~q$tUM--n=;_ zF)`70Xi0`ioJEBA{PWLm5o88okfAhd6!X-n=F#E9BbRH-moL|lb>b1oxft>W(?mzF zX%g?2qKlO_gl);MP+`g$(V+qgSwLrtf&{K3kFwWdr0xU$D&Y5{I9APbs;jGwfC{!@ zB#d^3jVk{0JK+1$5~YZhooX_MB{2{95uS86oZinr+ii??cB>#lP5Y^Iv%dgV13wh3 zmVLhm{sI4QJbn7KC1Dzegwd&#Nmf`QQ&*fK2vcOU#Hzo2>7|!yk>_lQG9~e*C<>L* zYX1ya4$J`_7K==l@7(u2;OD?!@Nji!&z?1|VP#UcI`zw6v6Ad`z0fi_+rMiTG#=nBrs_>aVY_cfuGW=gyt$ zqz?y^pPW=d@TH)dsZy){Z%lMdv5E<{;}9{l?k6(Y@vx-QFjcvt(ewD2PV8C^rX!FwH;c6@fwLr< z6S#cu6^Yj2 zHQ4#mm-WjzZQ>dYXytpX$STc@j>0Fw6)j|)su^=)1VT28%%P~|-BNATxJ4-dk4`h>H}#Q?{*{EsZ*!AOiNf!Mb_x&FI>3L z#9zZ zBrL7@ZIgb|Nu&}P?>42D>K!|F)Fa;xrOL9h?3giQvspDb*^;=|19des?5%vJN4Y$ zTo#Cb$$G*zpitHo{!B%N4)@#(FTBuC0mIcCt7~#US9jig^UZeo|0*C?eBEOVtVw*U z&UPZC;tVMGd2{B>aZ@-4a|!wuI|6pL<>lo@KZQ>vm>m!#VL3TD&e^kP8*TdlJKVnK zo_lU@V`JkX>2TFjYKK}6a|&~x3>oF(dJx`A3pX@0gcRax4UD5f>kwR-v04KQl$>0{ zc;k&XI&sfExc5(i1tNYD#LfP<5^7qsi;(troDg2ep)4pUsIRD~Fa&p~8xLkvi*{T{ zdG^_7t7RcYE!vG0-;cO$(Jo-^R&}_EqH`(CvokU>rfk@-;Q{E-Fe)Mj$Y9rS1N%1k zyoQnMEG#TMA5}z)1s){sI9)pHfWZtCME-NcDUY^*$fB$);rz@rb zQd~p5a*`-edNWRrN5NfU)6>(VrJ`{WxdFr7Dpjait=LHWQ)gO4X*JDq5A5X#3)1Rs zBd-vT2|$$WR|Uc?WL2l2W?R}!ucN3}PufjlCFE--gLzfTXAsgzxa9+#?F&vHuEb z{FqKQ8OQ*#fE*23@;S*T$}0H84HRLL>;T$;7NA+c_nkm5?!{Gzf&9Qdlg5uIHz3(` zU?h+S6amG2EiEk_yKv#cvE#>&&nPS`OvuX03RF~77=FLsh{a;Y_uqeS4h#&$>gwwH zE?&IYx_kHTpW52m>T&IL;2LlPxFr)H`99n?GF;+5-KT|cf-L(8pa2*Rj3-vDTJ^x1 zHERm0s;UA>Nl8Y0e7q4128}=Xe;Q&TOn zBsko`iI9AEcQ;od_U4;!*5UOa{&iX=P%l;|@vxi4?;tHctO(}hJG2;3~%3Z@=BPZQHh8`1nWQj99%%v-p4;Y30)a$pQ?n5?~4l zSMS)djauK2i;FYp_XPc3A2a}0ZvFc8jGY<~ z*K@kc<*Fz-j(AC7(}3Rt?`__^87tVv3}AW`C5}%~6PMS2S z`MKwwGjH6uVPZ0l*JFZaQ&W?y4qfDOQ0Am46nMcOX`n8`2QfB*gE4?g%{TtY&EXF&Dz^cb(d{`$a|Uw+B7 z#hiHrI1ZeWOhBvE0{`MFpvWA0_0?CSyw1@-k(F?7-_uV&Jx^ABloYlECnc&#b18C> zkH<4pu(G)g2HoY$mkneV`tAuKcUDr?O{sLZr1D(^>VSWvsMKD)desV4r>w&Ju3fv9 ziC~;qIaB08z>PF#H3FF=Z8%nb<&{^u+uPeuNnyAO+?H}2!ZNBX&?9<-CHoS-U-$02 z@AfmxI1Of-^73*6RhNNSC>1$HGMvv&I#EPV=Co=v{Sc4Ycst<)8vVK7$Lmk?Ydu8!XyyRAFAT96o3Bt=LW2H zMO>goBM%4jKWmbQKnRvuD-GZ5j=eL^;>HLJsE*@!q0_Vhs}f&~i} z_$8yINUjcw5*;MXn@8YJVUil=wu4$Bp9kT=AXnLmOAgZUUT#RgrnSe68Iz;+pF#az ze&ufm4jj0_0P$Kn{$NH%M!Kz`9n7PArD_D?rCrOE>Q*F8MF}*9MapYX1wwqO;-WXr zgQQh-cV4=5$s8(3CQh7~FA_Fa)FFR7orOu3G)w+hD9uC}8wz5^j2TmwFJEqW)dFQ4 zu80thb0Xa*)vWc|+wrNvEX3hcT&73*28e%Mt zI(n$o%Kzu*=O=5f;YgY}^fq$nb(UE-7a{3qpM93ro{qOOfm!leDd6W-qSAf?EG;Q1 zc?4o<$yZ-}m4fblNY_yAk(Za3XxqW^>01bWQn4CQW>20zeR}-dxpRF^Kh6#W0b-!5 zNqOOg7Zx5nb}UzfQHP{6MtT~oISOrBDUkixV~_a|2UZ6&+?I`j3d8wqWbYu|FE?db zzWnjWA2*=uEreEs#;@o=YUc%4RxjU_b;3k!o46%{`Cu2tDcl;0o9 z9h_P?+}pnIcgON4NMNzmT@U;f`XV}1BfwS+{%=A+nH?P+W@l%o2{FVH?b@ax$gM_z zuU>;K+*40I)hHd%BN8tOdJ~}3ShL8?Gw{J5QLe2D=(U7&A?o1t^mGHAwPjsq2B#Hb zIt3X6jf16OyaqEEgjJWV$sMp)MCBqB@#jvQIB{U>)~%Or-@a|QiJRdLihitwfmktC zu3TxXTer?|)6{z9#l-Eqtfnq%)V@cG~Rr<=EK-SWKt=FOWX0{#%bzX_<8ydO~R5ftJv6o;@Vw+6KW$R?q4=gvKV zPGt^yN6J?#u$)|gOG>M9>HGTnjI(FYnje1nVe=PXd{GCz(k`(>ojlnqi>Q5+D_5>G zOJ?gAYnylMnrdQ1xk{U)G9RjBAC~kQ8c$ZU`;ZP(Rfi*$%Ocy-(qh!s)<%yUInoJ9 za20=FkX_Oixgqxlje>%L3Airk((8JYp>~*pQhky&IcUg5OodS_b2D&K zG;UI9X=z4rad9%TMi@6ya<$80FFGW@No$pWv}YM-44pc4DkL%1qgncn{pp~HpES`C z*-B4n6Nm@_Wwilxu%Z-U{)MpkJ=dkb7|zvrr{nEzPXK#&`Gb-50cW&XqK+ zoR8jY(dyN!S0e9bz_;R%SN-Tz`cb}n5ge`i_U${006QkBu~DqitEHuw_O~iraq2_? z!F(Tcxm$&#l@Yj6#O*b$Y3|o^wC*03rRU9?S6x$6Q%ohMG6IkQ3#5Z|9$b3d(xpq4 zo7~bG*1PtTMN=n=r7ca3j${-`yA9^={rmSfN(O6@)eJk4_Ny}%1VIv0n$w02vqX7$ zc>*#=H3F|)tlXhlS|=P7!kOaFqoMXDp)~v5HiMWo(DWL6_Ut(=E7>YWiP{_IpjIMH zbzP%j< z>4J6+6LM;tmG-a#4MM5z%$YMWx6(~Ly<)|R1l*jud5SaxC1TAS*{jGEvTO(@0Tq)c zPp*QL4Z67ot;~JCojZ5_D0_mp-TYaVZxxJI+G}cRDpE}2 zRj}84?X}k)fkI1W3bY^gsIx%^5P_w&K4Uw^UXVdcvi9qAfyJXaX!mAJ?r-?$2ic6j zE?McWO-;9R#8e?ZMgpyJ?hat1-mGnh+)Ya$2QrLu;-oen7t=@jzqn%-91#>09@#eSe~xa{P!cO1}4eYfP$L>rto3I{Ze zt;qLj8ayImXxLBG-0Ra~YMUycNdW!TZ`%>l^>x&?9q_Zss;qVI4{tbBQkx-68-DQ^ jB>n#KLQK@`Y9D2Zm_ee00000NkvXXu0mjf72dv# 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 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 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 279/326] 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 280/326] 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 281/326] 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 282/326] 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 283/326] 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 284/326] 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 285/326] 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 286/326] 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 287/326] 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 288/326] 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 289/326] 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 290/326] 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 291/326] 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 292/326] 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 293/326] 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 294/326] 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 295/326] 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 296/326] 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 297/326] 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 298/326] 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 299/326] 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 300/326] 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 301/326] 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 302/326] 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 303/326] 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 304/326] 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 305/326] 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 306/326] 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 307/326] 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 cb1784a846901a15673575d25d2dcfc92ce85515 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:05:28 +0900 Subject: [PATCH 308/326] 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 309/326] 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 310/326] 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 311/326] 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 312/326] 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 313/326] 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 314/326] 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 315/326] 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 316/326] 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 317/326] 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 318/326] 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 319/326] 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 320/326] 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 321/326] 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 322/326] 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 323/326] 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 324/326] 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 28eae5d26b0a1e8b69cfa4dbf7ea6b5f03f62b9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 19:03:22 +0900 Subject: [PATCH 325/326] 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 a50ca0a1edba6a40af2ad24cc30c1abb86a6a95d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 20:00:17 +0900 Subject: [PATCH 326/326] 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); });