From f1535b74beb185c83f76d7e7e3a54e55b6c32a81 Mon Sep 17 00:00:00 2001 From: Kaleb Date: Sun, 13 Feb 2022 02:16:06 -0500 Subject: [PATCH 01/79] Give Spun Out mod dynamic spin rate --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 098c639949..b900fa3274 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -45,7 +45,8 @@ namespace osu.Game.Rulesets.Osu.Mods // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; - spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * 0.03f)); + float rotationSpeed = (float)(spinner.HitObject.SpinsRequired / spinner.HitObject.Duration / 1.01); + spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); } } } From 585bd541f319ce316c8c4eef80aeb4247449b232 Mon Sep 17 00:00:00 2001 From: Kaleb Date: Sun, 13 Feb 2022 02:38:49 -0500 Subject: [PATCH 02/79] Add missing parentheses to RPM calculation --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index b900fa3274..4725a43a77 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Mods // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; - float rotationSpeed = (float)(spinner.HitObject.SpinsRequired / spinner.HitObject.Duration / 1.01); + float rotationSpeed = (float)(spinner.HitObject.SpinsRequired / (spinner.HitObject.Duration / 1.01)); spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); } } From df9535d195700205380bc624f2a9999cfa9e228c Mon Sep 17 00:00:00 2001 From: Kaleb Date: Sun, 13 Feb 2022 14:28:40 -0500 Subject: [PATCH 03/79] Update RPM calculation for readability Multiply the 1.01 factor to the resulting RPM, not to the duration. --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 4725a43a77..9be0dc748a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -45,7 +45,10 @@ namespace osu.Game.Rulesets.Osu.Mods // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; - float rotationSpeed = (float)(spinner.HitObject.SpinsRequired / (spinner.HitObject.Duration / 1.01)); + + // multiply the SPM by 1.01 to ensure that the spinner is completed. if the calculation is left exact, + // some spinners may not complete due to very minor decimal loss during calculation + float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration); spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); } } From c1777f20e114da75a51c6e3fc38ad5f637b4900d Mon Sep 17 00:00:00 2001 From: Kaleb Date: Mon, 14 Feb 2022 03:11:44 -0500 Subject: [PATCH 04/79] Fix Spun Out tests Change 'unaffected by mods' test to use dynamic RPM value instead of a fixed value --- .../Mods/TestSceneOsuModSpunOut.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 24e69703a6..29d7e7b4d6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -48,7 +48,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => { var counter = Player.ChildrenOfType().SingleOrDefault(); - return counter != null && Precision.AlmostEquals(counter.Result.Value, 286, 1); + var spinner = Player.ChildrenOfType().FirstOrDefault(); + + if (counter == null || spinner == null) + return false; + + // ignore cases where the spinner hasn't started as these lead to false-positives + if (Precision.AlmostEquals(counter.Result.Value, 0, 1)) + return false; + + double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; + float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration); + + return Precision.AlmostEquals(counter.Result.Value, rotationSpeed * 1000 * 60, 1); } }); } From 95b1bffffeceff678762ccb32e329a0b9277af6a Mon Sep 17 00:00:00 2001 From: Kaleb Date: Mon, 14 Feb 2022 03:45:02 -0500 Subject: [PATCH 05/79] Add test to ensure spinners only complete No bonus or a non-300 judgement --- .../Mods/TestSceneOsuModSpunOut.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 29d7e7b4d6..e71377a505 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -57,7 +58,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods if (Precision.AlmostEquals(counter.Result.Value, 0, 1)) return false; - double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration); return Precision.AlmostEquals(counter.Result.Value, rotationSpeed * 1000 * 60, 1); @@ -65,6 +65,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }); } + [Test] + public void TestSpinnerOnlyComplete() => CreateModTest(new ModTestData + { + Mod = new OsuModSpunOut(), + Autoplay = false, + Beatmap = singleSpinnerBeatmap, + PassCondition = () => + { + var spinner = Player.ChildrenOfType().SingleOrDefault(); + var gameplayClockContainer = Player.ChildrenOfType().SingleOrDefault(); + + if (spinner == null || gameplayClockContainer == null) + return false; + + if (!Precision.AlmostEquals(gameplayClockContainer.CurrentTime, spinner.HitObject.StartTime + spinner.HitObject.Duration, 200.0f)) + return false; + + return Precision.AlmostEquals(spinner.Progress, 1.0f, 0.05f) && Precision.AlmostEquals(spinner.GainedBonus.Value, 0, 1); + } + }); + private Beatmap singleSpinnerBeatmap => new Beatmap { HitObjects = new List From 3d3f0a89c2c1b933eff1a7f8575ba7ac6a890f52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 16:46:53 +0900 Subject: [PATCH 06/79] Remove legacy `RulesetID` property from `BeatmapInfo` --- osu.Game/Beatmaps/BeatmapInfo.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index e4bfd768b7..305b3979a0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -152,18 +152,6 @@ namespace osu.Game.Beatmaps #region Compatibility properties - [Ignored] - public int RulesetID - { - set - { - if (!string.IsNullOrEmpty(Ruleset.InstantiationInfo)) - throw new InvalidOperationException($"Cannot set a {nameof(RulesetID)} when {nameof(Ruleset)} is already set to an actual ruleset."); - - Ruleset.OnlineID = value; - } - } - [Ignored] [Obsolete("Use BeatmapInfo.Difficulty instead.")] // can be removed 20220719 public BeatmapDifficulty BaseDifficulty From 7a69de0060c2c55d731fd89f3a62d72eeb93c424 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 16:57:28 +0900 Subject: [PATCH 07/79] Split out realm portion of `RulesetStore` --- osu.Game/OsuGameBase.cs | 7 +- osu.Game/Rulesets/RealmRulesetStore.cs | 101 +++++++++++++++++++++++++ osu.Game/Rulesets/RulesetStore.cs | 101 +++---------------------- 3 files changed, 115 insertions(+), 94 deletions(-) create mode 100644 osu.Game/Rulesets/RealmRulesetStore.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0b2644d5ba..d89e1e8193 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -22,6 +22,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; @@ -109,7 +110,7 @@ namespace osu.Game protected SkinManager SkinManager { get; private set; } - protected RulesetStore RulesetStore { get; private set; } + protected RealmRulesetStore RulesetStore { get; private set; } protected RealmKeyBindingStore KeyBindingStore { get; private set; } @@ -200,9 +201,11 @@ namespace osu.Game dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory)); - dependencies.Cache(RulesetStore = new RulesetStore(realm, Storage)); + dependencies.CacheAs(RulesetStore = new RealmRulesetStore(realm, Storage)); dependencies.CacheAs(RulesetStore); + Decoder.RegisterDependencies(RulesetStore); + // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts // after initial usages below. It can be moved once a direction is established for handling re-subscription. // See https://github.com/ppy/osu/pull/16547 for more discussion. diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs new file mode 100644 index 0000000000..f42bf06da4 --- /dev/null +++ b/osu.Game/Rulesets/RealmRulesetStore.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 System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Database; + +#nullable enable + +namespace osu.Game.Rulesets +{ + public class RealmRulesetStore : RulesetStore + { + public override IEnumerable AvailableRulesets => availableRulesets; + + private readonly List availableRulesets = new List(); + + public RealmRulesetStore(RealmAccess realm, Storage? storage = null) + : base(storage) + { + prepareDetachedRulesets(realm); + } + + private void prepareDetachedRulesets(RealmAccess realmAccess) + { + realmAccess.Write(realm => + { + var rulesets = realm.All(); + + List instances = LoadedAssemblies.Values + .Select(r => Activator.CreateInstance(r) as Ruleset) + .Where(r => r != null) + .Select(r => r.AsNonNull()) + .ToList(); + + // add all legacy rulesets first to ensure they have exclusive choice of primary key. + foreach (var r in instances.Where(r => r is ILegacyRuleset)) + { + if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } + + // add any other rulesets which have assemblies present but are not yet in the database. + foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) + { + if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) + { + var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + + if (existingSameShortName != null) + { + // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. + // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. + // in such cases, update the instantiation info of the existing entry to point to the new one. + existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + } + else + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } + } + + List detachedRulesets = new List(); + + // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. + foreach (var r in rulesets.OrderBy(r => r.OnlineID)) + { + try + { + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); + + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); + + // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution. + // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw. + resolvedType.Assembly.GetTypes(); + + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + r.Available = true; + + detachedRulesets.Add(r.Clone()); + } + catch (Exception ex) + { + r.Available = false; + Logger.Log($"Could not load ruleset {r}: {ex.Message}"); + } + } + + availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); + }); + } + } +} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index dd25005006..6f88d97a58 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -7,34 +7,26 @@ using System.IO; using System.Linq; using System.Reflection; using osu.Framework; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Database; #nullable enable namespace osu.Game.Rulesets { - public class RulesetStore : IDisposable, IRulesetStore + public abstract class RulesetStore : IDisposable, IRulesetStore { - private readonly RealmAccess realmAccess; - private const string ruleset_library_prefix = @"osu.Game.Rulesets"; - private readonly Dictionary loadedAssemblies = new Dictionary(); + protected readonly Dictionary LoadedAssemblies = new Dictionary(); /// /// All available rulesets. /// - public IEnumerable AvailableRulesets => availableRulesets; + public abstract IEnumerable AvailableRulesets { get; } - private readonly List availableRulesets = new List(); - - public RulesetStore(RealmAccess realm, Storage? storage = null) + protected RulesetStore(Storage? storage = null) { - realmAccess = realm; - // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. loadFromAppDomain(); @@ -53,8 +45,6 @@ namespace osu.Game.Rulesets var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets"); if (rulesetStorage != null) loadUserRulesets(rulesetStorage); - - addMissingRulesets(); } /// @@ -95,80 +85,7 @@ namespace osu.Game.Rulesets if (domainAssembly != null) return domainAssembly; - return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); - } - - private void addMissingRulesets() - { - realmAccess.Write(realm => - { - var rulesets = realm.All(); - - List instances = loadedAssemblies.Values - .Select(r => Activator.CreateInstance(r) as Ruleset) - .Where(r => r != null) - .Select(r => r.AsNonNull()) - .ToList(); - - // add all legacy rulesets first to ensure they have exclusive choice of primary key. - foreach (var r in instances.Where(r => r is ILegacyRuleset)) - { - if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) - realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); - } - - // add any other rulesets which have assemblies present but are not yet in the database. - foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) - { - if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) - { - var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); - - if (existingSameShortName != null) - { - // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. - // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. - // in such cases, update the instantiation info of the existing entry to point to the new one. - existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; - } - else - realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); - } - } - - List detachedRulesets = new List(); - - // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. - foreach (var r in rulesets.OrderBy(r => r.OnlineID)) - { - try - { - var resolvedType = Type.GetType(r.InstantiationInfo) - ?? throw new RulesetLoadException(@"Type could not be resolved"); - - var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo - ?? throw new RulesetLoadException(@"Instantiation failure"); - - // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution. - // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw. - resolvedType.Assembly.GetTypes(); - - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; - r.Available = true; - - detachedRulesets.Add(r.Clone()); - } - catch (Exception ex) - { - r.Available = false; - Logger.Log($"Could not load ruleset {r}: {ex.Message}"); - } - } - - availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); - }); + return LoadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); } private void loadFromAppDomain() @@ -214,7 +131,7 @@ namespace osu.Game.Rulesets { string? filename = Path.GetFileNameWithoutExtension(file); - if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) + if (LoadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) return; try @@ -229,17 +146,17 @@ namespace osu.Game.Rulesets private void addRuleset(Assembly assembly) { - if (loadedAssemblies.ContainsKey(assembly)) + if (LoadedAssemblies.ContainsKey(assembly)) return; // the same assembly may be loaded twice in the same AppDomain (currently a thing in certain Rider versions https://youtrack.jetbrains.com/issue/RIDER-48799). // as a failsafe, also compare by FullName. - if (loadedAssemblies.Any(a => a.Key.FullName == assembly.FullName)) + if (LoadedAssemblies.Any(a => a.Key.FullName == assembly.FullName)) return; try { - loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); + LoadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); } catch (Exception e) { From 0138f22c8d12b81cc6343798c8be1661a45f8524 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:13:51 +0900 Subject: [PATCH 08/79] Update existing usages to point to `RealmRulesetStore` --- .../Database/BeatmapImporterTests.cs | 50 +++++++++---------- osu.Game.Tests/Database/RulesetStoreTests.cs | 8 +-- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- .../Background/TestSceneUserDimBackgrounds.cs | 2 +- .../TestSceneManageCollectionsDialog.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../TestSceneMultiplayerReadyButton.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 2 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 2 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../SongSelect/TestSceneFilterControl.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 2 +- .../SongSelect/TestSceneTopLocalRank.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 2 +- 22 files changed, 49 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 2c7d0211a0..9d67381b5a 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using (var importer = new BeatmapModelManager(realm, storage)) - using (new RulesetStore(realm, storage)) + using (new RealmRulesetStore(realm, storage)) { Live? beatmapSet; @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using (var importer = new BeatmapModelManager(realm, storage)) - using (new RulesetStore(realm, storage)) + using (new RealmRulesetStore(realm, storage)) { Live? beatmapSet; @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using (var importer = new BeatmapModelManager(realm, storage)) - using (new RulesetStore(realm, storage)) + using (new RealmRulesetStore(realm, storage)) { Live? imported; @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); await LoadOszIntoStore(importer, realm.Realm); }); @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -201,7 +201,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -245,7 +245,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); @@ -265,7 +265,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -314,7 +314,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -366,7 +366,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -414,7 +414,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -463,7 +463,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -496,7 +496,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var progressNotification = new ImportProgressNotification(); @@ -532,7 +532,7 @@ namespace osu.Game.Tests.Database }; using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -582,7 +582,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -606,7 +606,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var store = new RealmRulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Realm); @@ -638,7 +638,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new NonOptimisedBeatmapImporter(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -662,7 +662,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -688,7 +688,7 @@ namespace osu.Game.Tests.Database RunTestWithRealm((realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var metadata = new BeatmapMetadata { @@ -734,7 +734,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) @@ -751,7 +751,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -787,7 +787,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -829,7 +829,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -880,7 +880,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); await importer.Import(temp); diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index 7544142b70..f48b5cba11 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realm, storage); + var rulesets = new RealmRulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, realm.Realm.All().Count()); @@ -26,8 +26,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realm, storage); - var rulesets2 = new RulesetStore(realm, storage); + var rulesets = new RealmRulesetStore(realm, storage); + var rulesets2 = new RealmRulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, rulesets2.AvailableRulesets.Count()); @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realm, storage); + var rulesets = new RealmRulesetStore(realm, storage); Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged); Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 9aa04dda92..c4bffbc0ab 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API)); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 40e7c0a844..9f708ace70 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index d4c13059da..e40dd58663 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 2a3b44d619..2c994576f3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 8c10a0d0d9..4dc7dc1c42 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3f9aec3a42..fc8c3f7f58 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 457b53ae61..381b9b58bd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 7d2ef8276d..1eebe781f1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index f84abc7443..af12046d76 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index bf06b6ad73..397c2d16d1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 609693e54b..6851dba721 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index c4ea78116a..0033a4ca74 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 3333afc88b..f8d62c9840 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 50b3f52047..05b97e333f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 578ea63b4e..a6f1762cb3 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 667fd08084..8c3cc02c83 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Realm)); + dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index b384061531..b7ec128596 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index d34aff8a23..e2b50e38c2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect { // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 8e5f76a2eb..39680d157b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index da4cf9c6e3..a0a1feff36 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Realm)); + dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler)); Dependencies.Cache(Realm); From 13086541f0884a4baec68afd1ee874830d6c366f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:12:57 +0900 Subject: [PATCH 09/79] Add static `RulesetStore` to `LegacyBeatmapDecoder` --- osu.Game/Beatmaps/Formats/Decoder.cs | 10 ++++++++++ osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 9 ++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 845ac20db0..8eb238a184 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using osu.Game.IO; +using osu.Game.Rulesets; namespace osu.Game.Beatmaps.Formats { @@ -37,6 +38,15 @@ namespace osu.Game.Beatmaps.Formats LegacyStoryboardDecoder.Register(); } + /// + /// Register dependencies for use with static decoder classes. + /// + /// A store containing all available rulesets (used by ). + public static void RegisterDependencies(RulesetStore rulesets) + { + LegacyBeatmapDecoder.RulesetStore = rulesets; + } + /// /// Retrieves a to parse a . /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 07ada8ecc4..fb7882c182 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Data; using System.IO; using System.Linq; using osu.Framework.Extensions; @@ -11,12 +12,15 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats { public class LegacyBeatmapDecoder : LegacyDecoder { + protected internal static RulesetStore RulesetStore; + private Beatmap beatmap; private ConvertHitObjectParser parser; @@ -40,6 +44,9 @@ namespace osu.Game.Beatmaps.Formats public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version) { + if (RulesetStore == null) + throw new InvalidOperationException($"Call {nameof(Decoder)}.{nameof(RegisterDependencies)} before using {nameof(LegacyBeatmapDecoder)}."); + // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) offset = FormatVersion < 5 ? 24 : 0; } @@ -158,7 +165,7 @@ namespace osu.Game.Beatmaps.Formats case @"Mode": int rulesetID = Parsing.ParseInt(pair.Value); - beatmap.BeatmapInfo.RulesetID = rulesetID; + beatmap.BeatmapInfo.Ruleset = RulesetStore.GetRuleset(rulesetID) ?? throw new ArgumentException("Ruleset is not available locally."); switch (rulesetID) { From d0efecfc9cff723d92a7e9289842737152e6dc61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:13:25 +0900 Subject: [PATCH 10/79] Add `RulesetStore` for use where realm is not present (ie. other projects) --- osu.Game/Rulesets/AssemblyRulesetStore.cs | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game/Rulesets/AssemblyRulesetStore.cs diff --git a/osu.Game/Rulesets/AssemblyRulesetStore.cs b/osu.Game/Rulesets/AssemblyRulesetStore.cs new file mode 100644 index 0000000000..b7378ccf61 --- /dev/null +++ b/osu.Game/Rulesets/AssemblyRulesetStore.cs @@ -0,0 +1,35 @@ +// 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.Extensions.ObjectExtensions; +using osu.Framework.Platform; + +#nullable enable + +namespace osu.Game.Rulesets +{ + public class AssemblyRulesetStore : RulesetStore + { + public override IEnumerable AvailableRulesets => availableRulesets; + + private readonly List availableRulesets = new List(); + + public AssemblyRulesetStore(Storage? storage = null) + : base(storage) + + { + List instances = LoadedAssemblies.Values + .Select(r => Activator.CreateInstance(r) as Ruleset) + .Where(r => r != null) + .Select(r => r.AsNonNull()) + .ToList(); + + // add all legacy rulesets first to ensure they have exclusive choice of primary key. + foreach (var r in instances.Where(r => r is ILegacyRuleset)) + availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } + } +} From 5477af08c5d972988df03b7834ff5ff0f039a9cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:21:06 +0900 Subject: [PATCH 11/79] Register an `AssemblyRulesetStore` in tests which don't use `OsuGameBase` --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 3 +++ osu.Game.Rulesets.Osu.Tests/StackingTest.cs | 6 ++++++ .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 7 +++++++ osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 7 +++++++ osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs | 7 +++++++ osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs | 3 +++ osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 - osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 6 ++++++ osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs | 6 ++++++ 9 files changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 1d207d04c7..0dd66be0b4 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Rulesets; using osu.Game.Tests.Resources; namespace osu.Game.Benchmarks @@ -18,6 +19,8 @@ namespace osu.Game.Benchmarks public override void SetUp() { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + using (var resources = new DllResourceStore(typeof(TestResources).Assembly)) using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz")) using (var reader = new ZipArchiveReader(archive)) diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index 871afdb09d..927c0c3e1e 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -18,6 +18,12 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class StackingTest { + [SetUp] + public void SetUp() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + [Test] public void TestStacking() { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 468cb7683c..ad088d298b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; @@ -29,6 +30,12 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyBeatmapDecoderTest { + [SetUp] + public void SetUp() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + [Test] public void TestDecodeBeatmapVersion() { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 2eb75259d9..b7989adcb8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Serialization; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; @@ -22,6 +23,12 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class OsuJsonDecoderTest { + [SetUp] + public void SetUp() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu"; diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 810ea5dbd0..64a52b2b01 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -9,12 +9,19 @@ using osu.Game.Tests.Resources; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Rulesets; namespace osu.Game.Tests.Beatmaps.IO { [TestFixture] public class OszArchiveReaderTest { + [SetUp] + public void SetUp() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + [Test] public void TestReadBeatmaps() { diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index 44a908b756..6cf760723a 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -8,6 +8,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; @@ -28,6 +29,8 @@ namespace osu.Game.Tests.Editing [SetUp] public void Setup() { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + patcher = new LegacyEditorBeatmapPatcher(current = new EditorBeatmap(new OsuBeatmap { BeatmapInfo = diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index fb7882c182..9626f39ec3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Data; using System.IO; using System.Linq; using osu.Framework.Extensions; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 8d622955b7..d5b7e3152b 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -34,6 +34,12 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } + [SetUp] + public void Setup() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + protected void Test(string name, params Type[] mods) { var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray()); diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 9f8811c7f9..78b2558640 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -22,6 +22,12 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } + [SetUp] + public void Setup() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + protected void Test(double expected, string name, params Mod[] mods) { // Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences. From 5ffd3ff82acbf76acc91accf666b76ab1255b3e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:25:16 +0900 Subject: [PATCH 12/79] Add xmldoc and allow constructing an `AssemblyRulesetStore` with a directory path --- osu.Game/Rulesets/AssemblyRulesetStore.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Rulesets/AssemblyRulesetStore.cs b/osu.Game/Rulesets/AssemblyRulesetStore.cs index b7378ccf61..7313a77aa5 100644 --- a/osu.Game/Rulesets/AssemblyRulesetStore.cs +++ b/osu.Game/Rulesets/AssemblyRulesetStore.cs @@ -11,12 +11,28 @@ using osu.Framework.Platform; namespace osu.Game.Rulesets { + /// + /// A ruleset store that populates from loaded assemblies (and optionally, assemblies in a storage). + /// public class AssemblyRulesetStore : RulesetStore { public override IEnumerable AvailableRulesets => availableRulesets; private readonly List availableRulesets = new List(); + /// + /// Create an assembly ruleset store that populates from loaded assemblies and an external location. + /// + /// An path containing ruleset DLLs. + public AssemblyRulesetStore(string path) + : this(new NativeStorage(path)) + { + } + + /// + /// Create an assembly ruleset store that populates from loaded assemblies and an optional storage source. + /// + /// An optional storage containing ruleset DLLs. public AssemblyRulesetStore(Storage? storage = null) : base(storage) From 98aaf83177ee1cb8777619936e31926b2167b399 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 15:57:37 +0900 Subject: [PATCH 13/79] Add a centralised constant for the osu URL schema protocol --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 12 ++++++------ osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 4 ++-- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++-- osu.Game/OsuGameBase.cs | 2 ++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 8def8005f1..cea4d510c1 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -409,26 +409,26 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(2, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); - Assert.AreEqual("osu://chan/#japanese", result.Links[1].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#japanese", result.Links[1].Url); } [Test] public void TestOsuProtocol() { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a custom protocol osu://chan/#english." }); + Message result = MessageFormatter.FormatMessage(new Message { Content = $"This is a custom protocol {OsuGameBase.OSU_PROTOCOL}chan/#english." }); Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); Assert.AreEqual(26, result.Links[0].Index); Assert.AreEqual(19, result.Links[0].Length); - result = MessageFormatter.FormatMessage(new Message { Content = "This is a [custom protocol](osu://chan/#english)." }); + result = MessageFormatter.FormatMessage(new Message { Content = $"This is a [custom protocol]({OsuGameBase.OSU_PROTOCOL}chan/#english)." }); Assert.AreEqual("This is a custom protocol.", result.DisplayContent); Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); Assert.AreEqual("#english", result.Links[0].Argument); Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(15, result.Links[0].Length); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 12b5f64559..d077868175 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -87,8 +87,8 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", 1, expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d7974004b1..4c477d58b6 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -269,10 +269,10 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); + handleMatches(time_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels - handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel); + handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); string empty = ""; while (space-- > 0) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0b2644d5ba..86390e7630 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -52,6 +52,8 @@ namespace osu.Game /// public partial class OsuGameBase : Framework.Game, ICanAcceptFiles { + public const string OSU_PROTOCOL = "osu://"; + public const string CLIENT_STREAM_NAME = @"lazer"; public const int SAMPLE_CONCURRENCY = 6; From 29c5683ba3ce2e7de362cac68949b3a46424cd12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:06:38 +0900 Subject: [PATCH 14/79] Add handling of beatmap links on startup --- .../TestSceneStartupBeatmapDisplay.cs | 22 ++++++++++++ .../TestSceneStartupBeatmapSetDisplay.cs | 22 ++++++++++++ osu.Game/OsuGame.cs | 36 ++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs new file mode 100644 index 0000000000..1efa24435e --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.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; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSceneStartupBeatmapDisplay : OsuGameTestScene + { + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://b/75" }); + + [Test] + public void TestBeatmapLink() + { + AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + } + } +} diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs new file mode 100644 index 0000000000..1339c514e4 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.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; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSceneStartupBeatmapSetDisplay : OsuGameTestScene + { + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://s/1" }); + + [Test] + public void TestBeatmapSetLink() + { + AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5b58dec0c3..390e96d768 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -150,6 +150,7 @@ namespace osu.Game protected SettingsOverlay Settings; private VolumeOverlay volume; + private OsuLogo osuLogo; private MainMenu menuScreen; @@ -898,8 +899,41 @@ namespace osu.Game if (args?.Length > 0) { string[] paths = args.Where(a => !a.StartsWith('-')).ToArray(); + if (paths.Length > 0) - Task.Run(() => Import(paths)); + { + string firstPath = paths.First(); + + if (firstPath.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) + { + handleOsuProtocolUrl(firstPath); + } + else + { + Task.Run(() => Import(paths)); + } + } + } + } + + private void handleOsuProtocolUrl(string url) + { + if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) + throw new ArgumentException("Invalid osu URL provided.", nameof(url)); + + string[] pieces = url.Split('/'); + + switch (pieces[2]) + { + case "s": + if (int.TryParse(pieces[3], out int beatmapSetId)) + ShowBeatmapSet(beatmapSetId); + break; + + case "b": + if (int.TryParse(pieces[3], out int beatmapId)) + ShowBeatmap(beatmapId); + break; } } From 420e2c538f01b1a1f387b7401a39d828698f83aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:48:30 +0900 Subject: [PATCH 15/79] Automatically use an `AssemblyRulesetStore` if no custom store is registered --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9626f39ec3..9dd2f27be5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Logging; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; @@ -44,7 +45,10 @@ namespace osu.Game.Beatmaps.Formats : base(version) { if (RulesetStore == null) - throw new InvalidOperationException($"Call {nameof(Decoder)}.{nameof(RegisterDependencies)} before using {nameof(LegacyBeatmapDecoder)}."); + { + Logger.Log($"A {nameof(RulesetStore)} was not provided via {nameof(Decoder)}.{nameof(RegisterDependencies)}; falling back to default {nameof(AssemblyRulesetStore)}."); + RulesetStore = new AssemblyRulesetStore(); + } // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) offset = FormatVersion < 5 ? 24 : 0; From cf1dd1ebd3fd4b849ed5249017363949f2112ea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:48:42 +0900 Subject: [PATCH 16/79] Disallow registering a null `RulesetStore` --- osu.Game/Beatmaps/Formats/Decoder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 8eb238a184..c1537d7240 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using JetBrains.Annotations; using osu.Game.IO; using osu.Game.Rulesets; @@ -42,9 +43,9 @@ namespace osu.Game.Beatmaps.Formats /// Register dependencies for use with static decoder classes. /// /// A store containing all available rulesets (used by ). - public static void RegisterDependencies(RulesetStore rulesets) + public static void RegisterDependencies([NotNull] RulesetStore rulesets) { - LegacyBeatmapDecoder.RulesetStore = rulesets; + LegacyBeatmapDecoder.RulesetStore = rulesets ?? throw new ArgumentNullException(nameof(rulesets)); } /// From 3abbf07fb3391d13e617409e1bfe16b5919243d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:52:14 +0900 Subject: [PATCH 17/79] Revert local registrations in test scenes --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 3 --- osu.Game.Rulesets.Osu.Tests/StackingTest.cs | 6 ------ .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 7 ------- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 7 ------- osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs | 7 ------- osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs | 3 --- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 6 ------ osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs | 6 ------ 8 files changed, 45 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 0dd66be0b4..1d207d04c7 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Rulesets; using osu.Game.Tests.Resources; namespace osu.Game.Benchmarks @@ -19,8 +18,6 @@ namespace osu.Game.Benchmarks public override void SetUp() { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - using (var resources = new DllResourceStore(typeof(TestResources).Assembly)) using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz")) using (var reader = new ZipArchiveReader(archive)) diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index 927c0c3e1e..871afdb09d 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -18,12 +18,6 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class StackingTest { - [SetUp] - public void SetUp() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - [Test] public void TestStacking() { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index ad088d298b..468cb7683c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; @@ -30,12 +29,6 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyBeatmapDecoderTest { - [SetUp] - public void SetUp() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - [Test] public void TestDecodeBeatmapVersion() { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index b7989adcb8..2eb75259d9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Serialization; -using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; @@ -23,12 +22,6 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class OsuJsonDecoderTest { - [SetUp] - public void SetUp() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu"; diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 64a52b2b01..810ea5dbd0 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -9,19 +9,12 @@ using osu.Game.Tests.Resources; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Rulesets; namespace osu.Game.Tests.Beatmaps.IO { [TestFixture] public class OszArchiveReaderTest { - [SetUp] - public void SetUp() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - [Test] public void TestReadBeatmaps() { diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index 6cf760723a..44a908b756 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -8,7 +8,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; @@ -29,8 +28,6 @@ namespace osu.Game.Tests.Editing [SetUp] public void Setup() { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - patcher = new LegacyEditorBeatmapPatcher(current = new EditorBeatmap(new OsuBeatmap { BeatmapInfo = diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index d5b7e3152b..8d622955b7 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -34,12 +34,6 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - [SetUp] - public void Setup() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - protected void Test(string name, params Type[] mods) { var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray()); diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 78b2558640..9f8811c7f9 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -22,12 +22,6 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - [SetUp] - public void Setup() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - protected void Test(double expected, string name, params Mod[] mods) { // Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences. From a029e418cfef55371f956f3b4e39be6126596e99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 17:06:04 +0900 Subject: [PATCH 18/79] Use `internal` instead of `protected internal` --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9dd2f27be5..e2a043490f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.Formats { public class LegacyBeatmapDecoder : LegacyDecoder { - protected internal static RulesetStore RulesetStore; + internal static RulesetStore RulesetStore; private Beatmap beatmap; From c869be87d1c817af8a4ae5382d5c17e124fb40ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 19 Feb 2022 20:53:04 +0900 Subject: [PATCH 19/79] Update `FlatFileWorkingBeatmap` to not require a ruleset store --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 5 ++--- osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 97a4c57bf0..10761bc315 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -77,10 +77,9 @@ namespace osu.Desktop.LegacyIpc case LegacyIpcDifficultyCalculationRequest req: try { - var ruleset = getLegacyRulesetFromID(req.RulesetId); - + WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile); + var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance(); Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray(); - WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset); return new LegacyIpcDifficultyCalculationResponse { diff --git a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs index 163da12b2e..cd8aa31ead 100644 --- a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Formats; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Skinning; namespace osu.Game.Beatmaps @@ -20,18 +19,16 @@ namespace osu.Game.Beatmaps { private readonly Beatmap beatmap; - public FlatFileWorkingBeatmap(string file, Func rulesetProvider, int? beatmapId = null) - : this(readFromFile(file), rulesetProvider, beatmapId) + public FlatFileWorkingBeatmap(string file, int? beatmapId = null) + : this(readFromFile(file), beatmapId) { } - private FlatFileWorkingBeatmap(Beatmap beatmap, Func rulesetProvider, int? beatmapId = null) + private FlatFileWorkingBeatmap(Beatmap beatmap, int? beatmapId = null) : base(beatmap.BeatmapInfo, null) { this.beatmap = beatmap; - beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.Ruleset.OnlineID).RulesetInfo; - if (beatmapId.HasValue) beatmap.BeatmapInfo.OnlineID = beatmapId.Value; } From 7ef710de2205e1af671fa5f4f0af76efc16373c5 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 19 Feb 2022 18:14:56 +0100 Subject: [PATCH 20/79] Allow exiting/minimizing on Android when on the initial cookie screen --- osu.Game/Screens/Menu/MainMenu.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 8b1bab52b3..2391903861 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -70,12 +70,16 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; + private readonly BindableBool allowExitingAndroid = new BindableBool(true); + [BackgroundDependencyLoader(true)] private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); + host.AllowExitingAndroid.AddSource(allowExitingAndroid); + if (host.CanExit) { AddInternal(exitConfirmOverlay = new ExitConfirmOverlay @@ -134,6 +138,8 @@ namespace osu.Game.Screens.Menu ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine)); break; } + + allowExitingAndroid.Value = state == ButtonSystemState.Initial; }; buttons.OnSettings = () => settings?.ToggleVisibility(); @@ -297,5 +303,11 @@ namespace osu.Game.Screens.Menu Schedule(loadSoloSongSelect); } + + protected override void Dispose(bool isDisposing) + { + host.AllowExitingAndroid.RemoveSource(allowExitingAndroid); + base.Dispose(isDisposing); + } } } From 11a11802edf98e666b25182e6740c31bc9c13981 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 19 Feb 2022 19:28:17 +0100 Subject: [PATCH 21/79] Ensure exiting is disallowed if we're not at the main menu --- osu.Android/OsuGameAndroid.cs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 050bf2b787..914a6f7502 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,7 +5,11 @@ using System; using Android.App; using Android.OS; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Game; +using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -17,6 +21,8 @@ namespace osu.Android [Cached] private readonly OsuGameActivity gameActivity; + private readonly BindableBool allowExiting = new BindableBool(); + public OsuGameAndroid(OsuGameActivity activity) : base(null) { @@ -67,16 +73,45 @@ namespace osu.Android } } + public override void SetHost(GameHost host) + { + base.SetHost(host); + host.AllowExitingAndroid.AddSource(allowExiting); + } + protected override void LoadComplete() { base.LoadComplete(); LoadComponentAsync(new GameplayScreenRotationLocker(), Add); } + protected override void ScreenChanged(IScreen current, IScreen newScreen) + { + base.ScreenChanged(current, newScreen); + + switch (newScreen) + { + case MainMenu _: + // allow the MainMenu to (dis)allow exiting based on its ButtonSystemState. + allowExiting.Value = true; + break; + + default: + allowExiting.Value = false; + break; + } + } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); + protected override void Dispose(bool isDisposing) + { + Host.AllowExitingAndroid.RemoveSource(allowExiting); + base.Dispose(isDisposing); + } + private class AndroidBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; From 7f4cc221d25c9d9733c5ec6189db61a3a0f0e2f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 18:33:27 +0900 Subject: [PATCH 22/79] Add API versioning --- osu.Game/Online/API/APIAccess.cs | 2 ++ osu.Game/Online/API/APIRequest.cs | 4 ++++ osu.Game/Online/API/DummyAPIAccess.cs | 2 ++ osu.Game/Online/API/IAPIProvider.cs | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index c5302a393c..8c9741b98b 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -36,6 +36,8 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } + public int APIVersion => 20220217; // We may want to pull this from the game version eventually. + public Exception LastLoginError { get; private set; } public string ProvidedUsername { get; private set; } diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 91148c177f..776ff5fd8f 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.IO.Network; @@ -112,6 +113,9 @@ namespace osu.Game.Online.API WebRequest = CreateWebRequest(); WebRequest.Failed += Fail; WebRequest.AllowRetryOnTimeout = false; + + WebRequest.AddHeader("x-api-version", API.APIVersion.ToString(CultureInfo.InvariantCulture)); + if (!string.IsNullOrEmpty(API.AccessToken)) WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 7131c3a7d4..f292e95bd1 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -33,6 +33,8 @@ namespace osu.Game.Online.API public string WebsiteRootUrl => "http://localhost"; + public int APIVersion => int.Parse(DateTime.Now.ToString("yyyyMMdd")); + public Exception LastLoginError { get; private set; } /// diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index a97eae77e3..470d46cd7f 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -57,6 +57,11 @@ namespace osu.Game.Online.API /// string WebsiteRootUrl { get; } + /// + /// The version of the API. + /// + int APIVersion { get; } + /// /// The last login error that occurred, if any. /// From 39d64e779cd576f5e0a7e62705c7063ceb49930a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 19:15:09 +0900 Subject: [PATCH 23/79] Handle API returned difficulty range for rooms --- osu.Game/Online/Rooms/Room.cs | 13 ++++++++++++ .../Components/StarRatingRangeDisplay.cs | 21 ++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index bbe854f2dd..6722dac51d 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -37,6 +37,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); + [JsonProperty("difficulty_range")] + public readonly Bindable DifficultyRange = new Bindable(); + [Cached] [JsonIgnore] public readonly Bindable Category = new Bindable(); @@ -228,5 +231,15 @@ namespace osu.Game.Online.Rooms public bool ShouldSerializeEndDate() => false; #endregion + + [JsonObject(MemberSerialization.OptIn)] + public class RoomDifficultyRange + { + [JsonProperty("min")] + public double Min; + + [JsonProperty("max")] + public double Max; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 95ecadd21a..f27f5d6741 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -12,6 +12,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Components @@ -71,6 +72,9 @@ namespace osu.Game.Screens.OnlinePlay.Components }; } + [Resolved] + private Room room { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -80,10 +84,21 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateRange(object sender, NotifyCollectionChangedEventArgs e) { - var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); + StarDifficulty minDifficulty; + StarDifficulty maxDifficulty; - StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); - StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); + if (room.DifficultyRange != null) + { + minDifficulty = new StarDifficulty(room.DifficultyRange.Value.Min, 0); + maxDifficulty = new StarDifficulty(room.DifficultyRange.Value.Max, 0); + } + else + { + var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); + + minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); + maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); + } minDisplay.Current.Value = minDifficulty; maxDisplay.Current.Value = maxDifficulty; From b43008b9f6ffc796e5927bf218230d9a4e806dc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:02:21 +0900 Subject: [PATCH 24/79] Add cover and count handling from newer response version --- osu.Game/Online/Rooms/Room.cs | 21 +++++++++++++++++++ .../Components/OnlinePlayBackgroundSprite.cs | 3 ++- .../Lounge/Components/PlaylistCountPill.cs | 13 +++++++----- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 6 ++++++ 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 6722dac51d..db24eeea41 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -37,6 +37,14 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); + [JsonProperty("current_playlist_item")] + [Cached] + public readonly Bindable CurrentPlaylistItem = new Bindable(); + + [JsonProperty("playlist_item_stats")] + [Cached] + public readonly Bindable PlaylistItemStats = new Bindable(); + [JsonProperty("difficulty_range")] public readonly Bindable DifficultyRange = new Bindable(); @@ -232,6 +240,19 @@ namespace osu.Game.Online.Rooms #endregion + [JsonObject(MemberSerialization.OptIn)] + public class RoomPlaylistItemStats + { + [JsonProperty("count_active")] + public int CountActive; + + [JsonProperty("count_total")] + public int CountTotal; + + [JsonProperty("ruleset_ids")] + public int[] RulesetIDs; + } + [JsonObject(MemberSerialization.OptIn)] public class RoomDifficultyRange { diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs index d46ff12279..2faa46e622 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs @@ -23,6 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { InternalChild = sprite = CreateBackgroundSprite(); + CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap()); Playlist.CollectionChanged += (_, __) => updateBeatmap(); updateBeatmap(); @@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateBeatmap() { - sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap; + sprite.Beatmap.Value = CurrentPlaylistItem.Value?.Beatmap ?? Playlist.GetCurrentItem()?.Beatmap; } protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index ef2c2df4a6..f387adfeb0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -1,13 +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.Collections.Specialized; using Humanizer; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -41,15 +42,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - Playlist.BindCollectionChanged(updateCount, true); + PlaylistItemStats.BindValueChanged(updateCount, true); } - private void updateCount(object sender, NotifyCollectionChangedEventArgs e) + private void updateCount(ValueChangedEvent valueChangedEvent) { + int activeItems = valueChangedEvent.NewValue.CountActive; + count.Clear(); - count.AddText(Playlist.Count.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); + count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); count.AddText(" "); - count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None)); + count.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None)); } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index c833621fbc..3148b74c9c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -32,6 +32,12 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Type { get; private set; } + [Resolved(typeof(Room))] + protected Bindable CurrentPlaylistItem { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable PlaylistItemStats { get; private set; } + [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } From b5348e04077910c65f497a88b5287d402dcf6427 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:05:32 +0900 Subject: [PATCH 25/79] Update ruleset filtering to use newly provided array return --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 9f917c978c..a521306d7e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.RulesetID == criteria.Ruleset.OnlineID); + matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID); if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); From ffa5291b74b76c3083a4694a0a5961b631fba3d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:37:36 +0900 Subject: [PATCH 26/79] Add fallback handling for item count to support different request types --- .../OnlinePlay/Lounge/Components/PlaylistCountPill.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index f387adfeb0..1aef41efbd 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -3,12 +3,10 @@ using Humanizer; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -42,12 +40,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - PlaylistItemStats.BindValueChanged(updateCount, true); + PlaylistItemStats.BindValueChanged(_ => updateCount(), true); + Playlist.BindCollectionChanged((_, __) => updateCount(), true); } - private void updateCount(ValueChangedEvent valueChangedEvent) + private void updateCount() { - int activeItems = valueChangedEvent.NewValue.CountActive; + int activeItems = PlaylistItemStats.Value?.CountActive ?? Playlist.Count; count.Clear(); count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); From c7e9cf904bbd777bdc878f893fd37624a321e39f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:38:26 +0900 Subject: [PATCH 27/79] Fix incorrect null check on now-bindable `DifficultyRange` --- .../Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index f27f5d6741..de56e6ff86 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Components StarDifficulty minDifficulty; StarDifficulty maxDifficulty; - if (room.DifficultyRange != null) + if (room.DifficultyRange.Value != null) { minDifficulty = new StarDifficulty(room.DifficultyRange.Value.Min, 0); maxDifficulty = new StarDifficulty(room.DifficultyRange.Value.Max, 0); From 2aa0364092819f821cd1be69c4d56e2150a1218d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 00:14:33 +0900 Subject: [PATCH 28/79] Fix null reference in tests during attempted ruleset filtering --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index a521306d7e..f57bff13ca 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID); + matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); From 113153e6a34becbae3934939172511b2f8ee591a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 00:25:00 +0900 Subject: [PATCH 29/79] Fix remaining filter tests --- osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 8dfd969c51..7ade173c9c 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -43,6 +43,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay if (ruleset != null) { + room.PlaylistItemStats.Value = new Room.RoomPlaylistItemStats + { + RulesetIDs = new[] { ruleset.OnlineID }, + }; + room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID, From 8d70b85e41c8bba1aa2c056554c66a59d0002f56 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 21 Feb 2022 20:20:24 +0100 Subject: [PATCH 30/79] Revert changes --- osu.Android/OsuGameAndroid.cs | 35 ------------------------------- osu.Game/Screens/Menu/MainMenu.cs | 12 ----------- 2 files changed, 47 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 914a6f7502..050bf2b787 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,11 +5,7 @@ using System; using Android.App; using Android.OS; using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Platform; -using osu.Framework.Screens; using osu.Game; -using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -21,8 +17,6 @@ namespace osu.Android [Cached] private readonly OsuGameActivity gameActivity; - private readonly BindableBool allowExiting = new BindableBool(); - public OsuGameAndroid(OsuGameActivity activity) : base(null) { @@ -73,45 +67,16 @@ namespace osu.Android } } - public override void SetHost(GameHost host) - { - base.SetHost(host); - host.AllowExitingAndroid.AddSource(allowExiting); - } - protected override void LoadComplete() { base.LoadComplete(); LoadComponentAsync(new GameplayScreenRotationLocker(), Add); } - protected override void ScreenChanged(IScreen current, IScreen newScreen) - { - base.ScreenChanged(current, newScreen); - - switch (newScreen) - { - case MainMenu _: - // allow the MainMenu to (dis)allow exiting based on its ButtonSystemState. - allowExiting.Value = true; - break; - - default: - allowExiting.Value = false; - break; - } - } - protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); - protected override void Dispose(bool isDisposing) - { - Host.AllowExitingAndroid.RemoveSource(allowExiting); - base.Dispose(isDisposing); - } - private class AndroidBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 2391903861..8b1bab52b3 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -70,16 +70,12 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; - private readonly BindableBool allowExitingAndroid = new BindableBool(true); - [BackgroundDependencyLoader(true)] private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); - host.AllowExitingAndroid.AddSource(allowExitingAndroid); - if (host.CanExit) { AddInternal(exitConfirmOverlay = new ExitConfirmOverlay @@ -138,8 +134,6 @@ namespace osu.Game.Screens.Menu ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine)); break; } - - allowExitingAndroid.Value = state == ButtonSystemState.Initial; }; buttons.OnSettings = () => settings?.ToggleVisibility(); @@ -303,11 +297,5 @@ namespace osu.Game.Screens.Menu Schedule(loadSoloSongSelect); } - - protected override void Dispose(bool isDisposing) - { - host.AllowExitingAndroid.RemoveSource(allowExitingAndroid); - base.Dispose(isDisposing); - } } } From 3eee505aa26569059ca0fddf35c202518a484453 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 21 Feb 2022 20:24:17 +0100 Subject: [PATCH 31/79] Update "exit" flow when pressing back on Android --- osu.Game/Screens/Menu/ExitConfirmOverlay.cs | 23 ++++++++++++++++++ osu.Game/Screens/Menu/MainMenu.cs | 27 +++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index a90b83c5fe..253ec17c28 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -3,6 +3,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -42,4 +43,26 @@ namespace osu.Game.Screens.Menu } } } + + /// + /// An that behaves as if the is always 0. + /// + /// This is useful for mobile devices using gesture navigation, where holding to confirm is not possible. + public class NoHoldExitConfirmOverlay : ExitConfirmOverlay, IKeyBindingHandler + { + public new bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + if (e.Action == GlobalAction.Back) + { + Progress.Value = 1; + Confirm(); + return true; + } + + return false; + } + } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 8b1bab52b3..7bc0cb48bf 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; @@ -89,6 +90,14 @@ namespace osu.Game.Screens.Menu } }); } + else if (host.CanSuspendToBackground) + { + AddInternal(exitConfirmOverlay = new NoHoldExitConfirmOverlay + { + // treat as if the UIHoldActivationDelay is always 0. see NoHoldExitConfirmOverlay xmldoc for more info. + Action = this.Exit + }); + } AddRangeInternal(new[] { @@ -148,6 +157,24 @@ namespace osu.Game.Screens.Menu private void confirmAndExit() { + if (host.CanSuspendToBackground) + { + // cancel the overlay as we're not actually exiting. + // this is the same action as 'onCancel' in `ConfirmExitDialog`. + exitConfirmOverlay.Abort(); + + // fade the track so the Bass.Pause() on suspend isn't as jarring. + const double fade_time = 500; + musicController.CurrentTrack + .VolumeTo(0, fade_time, Easing.Out).Then() + .VolumeTo(1, fade_time, Easing.In); + + host.SuspendToBackground(); + + // on hosts that can only suspend, we don't ever want to exit the game. + return; + } + if (exitConfirmed) return; exitConfirmed = true; From cd3641137bc920ef042396125c0c323fd7875fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:02:54 +0100 Subject: [PATCH 32/79] Add `OsuColour` method mapping colours from basic theme to mod types --- osu.Game/Graphics/OsuColour.cs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 886ba7ef92..afedf36cad 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Utils; @@ -157,6 +158,36 @@ namespace osu.Game.Graphics } } + /// + /// Retrieves the main accent colour for a . + /// + public Color4 ForModType(ModType modType) + { + switch (modType) + { + case ModType.Automation: + return Blue1; + + case ModType.DifficultyIncrease: + return Red1; + + case ModType.DifficultyReduction: + return Lime1; + + case ModType.Conversion: + return Purple1; + + case ModType.Fun: + return Pink1; + + case ModType.System: + return Gray7; + + default: + throw new ArgumentOutOfRangeException(nameof(modType), modType, "Unknown mod type"); + } + } + /// /// Returns a foreground text colour that is supposed to contrast well with /// the supplied . From 5186693dad42b370b115da9a304b3fc7873470a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:03:07 +0100 Subject: [PATCH 33/79] Implement tiny mod switch --- .../UserInterface/TestSceneModSwitchTiny.cs | 85 +++++++++++++++++ osu.Game/Rulesets/UI/ModSwitchTiny.cs | 91 +++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs create mode 100644 osu.Game/Rulesets/UI/ModSwitchTiny.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs new file mode 100644 index 0000000000..dbde7ce425 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs @@ -0,0 +1,85 @@ +// 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 NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSwitchTiny : OsuTestScene + { + [Test] + public void TestOsu() => createSwitchTestFor(new OsuRuleset()); + + [Test] + public void TestTaiko() => createSwitchTestFor(new TaikoRuleset()); + + [Test] + public void TestCatch() => createSwitchTestFor(new CatchRuleset()); + + [Test] + public void TestMania() => createSwitchTestFor(new ManiaRuleset()); + + private void createSwitchTestFor(Ruleset ruleset) + { + AddStep("no colour scheme", () => Child = createContent(ruleset, null)); + + foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast()) + { + AddStep($"{scheme} colour scheme", () => Child = createContent(ruleset, scheme)); + } + + AddToggleStep("toggle active", active => this.ChildrenOfType().ForEach(s => s.Active.Value = active)); + } + + private static Drawable createContent(Ruleset ruleset, OverlayColourScheme? colourScheme) + { + var switchFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding(20), + ChildrenEnumerable = ruleset.CreateAllMods() + .GroupBy(mod => mod.Type) + .Select(group => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5), + ChildrenEnumerable = group.Select(mod => new ModSwitchTiny(mod)) + }) + }; + + if (colourScheme != null) + { + return new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayColourProvider), new OverlayColourProvider(colourScheme.Value)) + }, + Child = switchFlow + }; + } + + return switchFlow; + } + } +} diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs new file mode 100644 index 0000000000..093c2271b6 --- /dev/null +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -0,0 +1,91 @@ +// 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.Shapes; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +#nullable enable + +namespace osu.Game.Rulesets.UI +{ + public class ModSwitchTiny : CompositeDrawable + { + public BindableBool Active { get; } = new BindableBool(); + + private readonly IMod mod; + + private readonly Box background; + private readonly OsuSpriteText acronymText; + + private Color4 activeForegroundColour; + private Color4 inactiveForegroundColour; + + private Color4 activeBackgroundColour; + private Color4 inactiveBackgroundColour; + + public ModSwitchTiny(IMod mod) + { + this.mod = mod; + Size = new Vector2(73, 30); + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + acronymText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, + Font = OsuFont.Numeric.With(size: 24, weight: FontWeight.Black), + Text = mod.Acronym, + Margin = new MarginPadding + { + Top = 4 + } + } + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider? colourProvider) + { + inactiveBackgroundColour = colourProvider?.Background5 ?? colours.Gray3; + activeBackgroundColour = colours.ForModType(mod.Type); + + inactiveForegroundColour = colourProvider?.Background2 ?? colours.Gray5; + activeForegroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Active.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + acronymText.FadeColour(Active.Value ? activeForegroundColour : inactiveForegroundColour, 200, Easing.OutQuint); + background.FadeColour(Active.Value ? activeBackgroundColour : inactiveBackgroundColour, 200, Easing.OutQuint); + } + } +} From cfc41a0a366cc0c311cbee0cf1aa9ea37d87784a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:03:12 +0100 Subject: [PATCH 34/79] Implement small mod switch --- .../UserInterface/TestSceneModSwitchSmall.cs | 85 ++++++++++++++ osu.Game/Rulesets/UI/ModSwitchSmall.cs | 109 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs create mode 100644 osu.Game/Rulesets/UI/ModSwitchSmall.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs new file mode 100644 index 0000000000..447352b7a6 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs @@ -0,0 +1,85 @@ +// 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 NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSwitchSmall : OsuTestScene + { + [Test] + public void TestOsu() => createSwitchTestFor(new OsuRuleset()); + + [Test] + public void TestTaiko() => createSwitchTestFor(new TaikoRuleset()); + + [Test] + public void TestCatch() => createSwitchTestFor(new CatchRuleset()); + + [Test] + public void TestMania() => createSwitchTestFor(new ManiaRuleset()); + + private void createSwitchTestFor(Ruleset ruleset) + { + AddStep("no colour scheme", () => Child = createContent(ruleset, null)); + + foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast()) + { + AddStep($"{scheme} colour scheme", () => Child = createContent(ruleset, scheme)); + } + + AddToggleStep("toggle active", active => this.ChildrenOfType().ForEach(s => s.Active.Value = active)); + } + + private static Drawable createContent(Ruleset ruleset, OverlayColourScheme? colourScheme) + { + var switchFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding(20), + ChildrenEnumerable = ruleset.CreateAllMods() + .GroupBy(mod => mod.Type) + .Select(group => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5), + ChildrenEnumerable = group.Select(mod => new ModSwitchSmall(mod)) + }) + }; + + if (colourScheme != null) + { + return new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayColourProvider), new OverlayColourProvider(colourScheme.Value)) + }, + Child = switchFlow + }; + } + + return switchFlow; + } + } +} diff --git a/osu.Game/Rulesets/UI/ModSwitchSmall.cs b/osu.Game/Rulesets/UI/ModSwitchSmall.cs new file mode 100644 index 0000000000..5d9d9075bc --- /dev/null +++ b/osu.Game/Rulesets/UI/ModSwitchSmall.cs @@ -0,0 +1,109 @@ +// 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.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +#nullable enable + +namespace osu.Game.Rulesets.UI +{ + public class ModSwitchSmall : CompositeDrawable + { + public BindableBool Active { get; } = new BindableBool(); + + private readonly IMod mod; + + private const float size = 60; + + private readonly SpriteIcon background; + private readonly SpriteIcon? modIcon; + + private Color4 activeForegroundColour; + private Color4 inactiveForegroundColour; + + private Color4 activeBackgroundColour; + private Color4 inactiveBackgroundColour; + + public ModSwitchSmall(IMod mod) + { + this.mod = mod; + + AutoSizeAxes = Axes.Both; + + FillFlowContainer contentFlow; + ModSwitchTiny tinySwitch; + + InternalChildren = new Drawable[] + { + background = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(size), + Icon = OsuIcon.ModBg + }, + contentFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(0, 4), + Direction = FillDirection.Vertical, + Child = tinySwitch = new ModSwitchTiny(mod) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Scale = new Vector2(0.6f), + Active = { BindTarget = Active } + } + } + }; + + if (mod.Icon != null) + { + contentFlow.Insert(-1, modIcon = new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(21), + Icon = mod.Icon.Value + }); + tinySwitch.Scale = new Vector2(0.3f); + } + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider? colourProvider) + { + inactiveForegroundColour = colourProvider?.Background5 ?? colours.Gray3; + activeForegroundColour = colours.ForModType(mod.Type); + + inactiveBackgroundColour = colourProvider?.Background2 ?? colours.Gray5; + activeBackgroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Active.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + modIcon?.FadeColour(Active.Value ? activeForegroundColour : inactiveForegroundColour, 200, Easing.OutQuint); + background.FadeColour(Active.Value ? activeBackgroundColour : inactiveBackgroundColour, 200, Easing.OutQuint); + } + } +} From 0d56693b7aee6221d16ac2341d86d70b5a805a55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:11:22 +0900 Subject: [PATCH 35/79] Fix test not always checking the final bonus value Due to the previous logic not waiting until the spinner had completed, there could be false negatives as the check runs too early, with a potential additional bonus spin occurring afterwards. --- .../Mods/TestSceneOsuModSpunOut.cs | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index e71377a505..cb8eceb213 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -8,13 +8,15 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning.Default; -using osu.Game.Screens.Play; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -66,25 +68,45 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestSpinnerOnlyComplete() => CreateModTest(new ModTestData + public void TestSpinnerGetsNoBonusScore() { - Mod = new OsuModSpunOut(), - Autoplay = false, - Beatmap = singleSpinnerBeatmap, - PassCondition = () => + DrawableSpinner spinner = null; + List results = new List(); + + CreateModTest(new ModTestData { - var spinner = Player.ChildrenOfType().SingleOrDefault(); - var gameplayClockContainer = Player.ChildrenOfType().SingleOrDefault(); + Mod = new OsuModSpunOut(), + Autoplay = false, + Beatmap = singleSpinnerBeatmap, + PassCondition = () => + { + // Bind to the first spinner's results for further tracking. + if (spinner == null) + { + // We only care about the first spinner we encounter for this test. + var nextSpinner = Player.ChildrenOfType().SingleOrDefault(); - if (spinner == null || gameplayClockContainer == null) - return false; + if (nextSpinner == null) + return false; - if (!Precision.AlmostEquals(gameplayClockContainer.CurrentTime, spinner.HitObject.StartTime + spinner.HitObject.Duration, 200.0f)) - return false; + spinner = nextSpinner; + spinner.OnNewResult += (o, result) => results.Add(result); - return Precision.AlmostEquals(spinner.Progress, 1.0f, 0.05f) && Precision.AlmostEquals(spinner.GainedBonus.Value, 0, 1); - } - }); + results.Clear(); + } + + // we should only be checking the bonus/progress after the spinner has fully completed. + if (!results.OfType().Any(r => r.TimeCompleted != null)) + return false; + + return + results.Any(r => r.Type == HitResult.SmallTickHit) + && !results.Any(r => r.Type == HitResult.LargeTickHit) + && Precision.AlmostEquals(spinner.Progress, 1.0f, 0.05f) + && Precision.AlmostEquals(spinner.GainedBonus.Value, 0, 1); + } + }); + } private Beatmap singleSpinnerBeatmap => new Beatmap { From 91acc9eec6a8859f665f64338420efc564faf33b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:36:08 +0900 Subject: [PATCH 36/79] Remove checks which are still going to occasionally fail due to pooling --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index cb8eceb213..93c4bd96de 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -101,9 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods return results.Any(r => r.Type == HitResult.SmallTickHit) - && !results.Any(r => r.Type == HitResult.LargeTickHit) - && Precision.AlmostEquals(spinner.Progress, 1.0f, 0.05f) - && Precision.AlmostEquals(spinner.GainedBonus.Value, 0, 1); + && !results.Any(r => r.Type == HitResult.LargeTickHit); } }); } From 9e279c3ebc9cdc23ba5d4a1d192642b314019de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:37:52 +0900 Subject: [PATCH 37/79] Fix completely incorrect judgement specification --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 93c4bd96de..e61720d8b9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -100,8 +100,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods return false; return - results.Any(r => r.Type == HitResult.SmallTickHit) - && !results.Any(r => r.Type == HitResult.LargeTickHit); + results.Any(r => r.Type == HitResult.SmallBonus) + && !results.Any(r => r.Type == HitResult.LargeBonus); } }); } From cde3d9c08ba8e3b9af8e8e6192c9febdfa306baa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:15:57 +0900 Subject: [PATCH 38/79] Change precedence order to favour playlist as a source for beatmap count --- .../OnlinePlay/Lounge/Components/PlaylistCountPill.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 1aef41efbd..e21439e931 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.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.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; @@ -46,7 +47,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateCount() { - int activeItems = PlaylistItemStats.Value?.CountActive ?? Playlist.Count; + int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null + // For now, use the playlist as the source of truth if it has any items. + // This allows the count to display correctly on the room screen (after joining a room). + ? Playlist.Count(i => !i.Expired) + : PlaylistItemStats.Value.CountActive; count.Clear(); count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); From f12044b03e97da4bbb883dd026c267f796345fe6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:31:08 +0900 Subject: [PATCH 39/79] Add mention of `PlaylistItem.Beatmap` being a placeholder in many cases --- osu.Game/Online/Rooms/PlaylistItem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 33718f050b..f696362cbb 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -62,6 +62,10 @@ namespace osu.Game.Online.Rooms [JsonProperty("beatmap_id")] private int onlineBeatmapId => Beatmap.OnlineID; + /// + /// A beatmap representing this playlist item. + /// In many cases, this will *not* contain any usable information apart from OnlineID. + /// [JsonIgnore] public IBeatmapInfo Beatmap { get; set; } = null!; From 057fd6c352c354367ba5056dc7ed8c92976391de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:37:42 +0900 Subject: [PATCH 40/79] Add mention of `StarRatingRangeDisplay` fallback scenario being wrong for multiplayer --- .../Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index de56e6ff86..0b673006ef 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -94,6 +94,8 @@ namespace osu.Game.Screens.OnlinePlay.Components } else { + // In multiplayer rooms, the beatmaps of playlist items will not be populated to a point this can be correct. + // Either populating them via BeatmapLookupCache or polling the API for the room's DifficultyRange will be required. var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); From 61b3280de157b86d6b6ad61558902c491e617fc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:47:00 +0900 Subject: [PATCH 41/79] Add missing property copies in `Room.CopyFrom` implementation --- osu.Game/Online/Rooms/Room.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d24cb7e74b..8ed37ad6b5 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -194,6 +194,8 @@ namespace osu.Game.Online.Rooms EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; QueueMode.Value = other.QueueMode.Value; + DifficultyRange.Value = other.DifficultyRange.Value; + PlaylistItemStats.Value = other.PlaylistItemStats.Value; if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); From ca0a04115374368184ca8363ac7026384b9c9dcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 16:45:04 +0900 Subject: [PATCH 42/79] Fix missing escaping causing test failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4c477d58b6..39a51876c2 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -269,7 +269,7 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); + handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); From ed008267d7a932c64dac5a3566af1bcdd145e415 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 16:45:18 +0900 Subject: [PATCH 43/79] Fix one more case of escaping not being present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 39a51876c2..333c554d44 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -272,7 +272,7 @@ namespace osu.Game.Online.Chat handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels - handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); + handleMatches(channel_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}chan/{{0}}", result, startIndex, LinkAction.OpenChannel); string empty = ""; while (space-- > 0) From 5efffa208acc118d8acf16cae9a609318e85b7b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 17:08:09 +0900 Subject: [PATCH 44/79] Add test coverage of beatmap set overlay actually showing requested beatmap --- .../TestSceneStartupBeatmapDisplay.cs | 34 ++++++++++++++++++- .../TestSceneStartupBeatmapSetDisplay.cs | 32 ++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs index 1efa24435e..961b7dedc3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs @@ -5,18 +5,50 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; namespace osu.Game.Tests.Visual.Navigation { public class TestSceneStartupBeatmapDisplay : OsuGameTestScene { - protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://b/75" }); + private const int requested_beatmap_id = 75; + private const int requested_beatmap_set_id = 1; + + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { $"osu://b/{requested_beatmap_id}" }); + + [SetUp] + public void Setup() => Schedule(() => + { + ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetBeatmapSetRequest gbr: + var apiBeatmapSet = CreateAPIBeatmapSet(); + apiBeatmapSet.OnlineID = requested_beatmap_set_id; + apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap + { + DifficultyName = "Target difficulty", + OnlineID = requested_beatmap_id, + }).ToArray(); + + gbr.TriggerSuccess(apiBeatmapSet); + return true; + } + + return false; + }; + }); [Test] public void TestBeatmapLink() { AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Beatmap.Value.OnlineID == requested_beatmap_id); } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs index 1339c514e4..1aa56896d3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs @@ -5,18 +5,48 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Navigation { public class TestSceneStartupBeatmapSetDisplay : OsuGameTestScene { - protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://s/1" }); + private const int requested_beatmap_set_id = 1; + + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { $"osu://s/{requested_beatmap_set_id}" }); + + [SetUp] + public void Setup() => Schedule(() => + { + ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetBeatmapSetRequest gbr: + + var apiBeatmapSet = CreateAPIBeatmapSet(); + apiBeatmapSet.OnlineID = requested_beatmap_set_id; + apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap + { + DifficultyName = "Target difficulty", + OnlineID = 75, + }).ToArray(); + gbr.TriggerSuccess(apiBeatmapSet); + return true; + } + + return false; + }; + }); [Test] public void TestBeatmapSetLink() { AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id); } } } From 6de4e05e492aa7ff03e068000ffdf83cb2dd65d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 17:14:00 +0900 Subject: [PATCH 45/79] Fix current selection not being correctly maintained when `BeatmapPicker` updates its display --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 59e8e8db3c..031442814d 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -183,7 +183,14 @@ namespace osu.Game.Overlays.BeatmapSet } starRatingContainer.FadeOut(100); - Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; + + // If a selection is already made, try and maintain it. + if (Beatmap.Value != null) + Beatmap.Value = Difficulties.FirstOrDefault(b => b.Beatmap.OnlineID == Beatmap.Value.OnlineID)?.Beatmap; + + // Else just choose the first available difficulty for now. + Beatmap.Value ??= Difficulties.FirstOrDefault()?.Beatmap; + plays.Value = BeatmapSet?.PlayCount ?? 0; favourites.Value = BeatmapSet?.FavouriteCount ?? 0; From d1d6847d3205d5ea1226dc063d7315b95cd47491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 22:24:37 +0100 Subject: [PATCH 46/79] Add comment about split usage in osu:// protocol link handling --- osu.Game/OsuGame.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 390e96d768..77eec004c0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -921,6 +921,9 @@ namespace osu.Game if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) throw new ArgumentException("Invalid osu URL provided.", nameof(url)); + // note that `StringSplitOptions.RemoveEmptyEntries` is not explicitly specified here + // in order to ensure that the protocol URL is valid (i.e. it has two slashes in the `osu://` part, + // causing the link content to be stored to the 2nd index rather than the 1st). string[] pieces = url.Split('/'); switch (pieces[2]) From d8fa443ea0eb21f607253aa9d8de679d94a51800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 23:22:00 +0100 Subject: [PATCH 47/79] Extract default mod switch measurements to constants For use later when specific sizes/scales of the mod switches are desired. --- osu.Game/Rulesets/UI/ModSwitchSmall.cs | 6 +++--- osu.Game/Rulesets/UI/ModSwitchTiny.cs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModSwitchSmall.cs b/osu.Game/Rulesets/UI/ModSwitchSmall.cs index 5d9d9075bc..676bbac95c 100644 --- a/osu.Game/Rulesets/UI/ModSwitchSmall.cs +++ b/osu.Game/Rulesets/UI/ModSwitchSmall.cs @@ -21,9 +21,9 @@ namespace osu.Game.Rulesets.UI { public BindableBool Active { get; } = new BindableBool(); - private readonly IMod mod; + public const float DEFAULT_SIZE = 60; - private const float size = 60; + private readonly IMod mod; private readonly SpriteIcon background; private readonly SpriteIcon? modIcon; @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.UI { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(size), + Size = new Vector2(DEFAULT_SIZE), Icon = OsuIcon.ModBg }, contentFlow = new FillFlowContainer diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs index 093c2271b6..b1d453f588 100644 --- a/osu.Game/Rulesets/UI/ModSwitchTiny.cs +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.UI { public BindableBool Active { get; } = new BindableBool(); + public const float DEFAULT_HEIGHT = 30; + private readonly IMod mod; private readonly Box background; @@ -36,7 +38,7 @@ namespace osu.Game.Rulesets.UI public ModSwitchTiny(IMod mod) { this.mod = mod; - Size = new Vector2(73, 30); + Size = new Vector2(73, DEFAULT_HEIGHT); InternalChild = new CircularContainer { From 2bea485af8da9bddd5fe404dd71e967a3c9b631b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Feb 2022 13:37:45 +0900 Subject: [PATCH 48/79] Fix currently playing text not showing in lounge --- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 3148b74c9c..7d1feb0316 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -91,9 +91,15 @@ namespace osu.Game.Screens.OnlinePlay Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true); } - protected virtual void UpdateSelectedItem() - => SelectedItem.Value = RoomID.Value == null || subScreenSelectedItem == null - ? Playlist.GetCurrentItem() - : subScreenSelectedItem.Value; + protected void UpdateSelectedItem() + { + if (RoomID.Value == null || subScreenSelectedItem == null) + { + SelectedItem.Value = CurrentPlaylistItem.Value ?? Playlist.GetCurrentItem(); + return; + } + + SelectedItem.Value = subScreenSelectedItem.Value; + } } } From 71a012bea6021ad559da8ccce2f92413bff80c21 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Feb 2022 13:42:47 +0900 Subject: [PATCH 49/79] Don't update count twice immediately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index e21439e931..a6bbcd548d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - PlaylistItemStats.BindValueChanged(_ => updateCount(), true); + PlaylistItemStats.BindValueChanged(_ => updateCount()); Playlist.BindCollectionChanged((_, __) => updateCount(), true); } From 87da650dfb5651c608562865a37db35a0e04c32e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 13:51:25 +0900 Subject: [PATCH 50/79] Update framework --- osu.Android.props | 2 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 24a0d20874..526ce959a6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index cd675e467b..1107089a46 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); + var dllStore = new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), this, true); defaultSkin = new DefaultLegacySkin(this); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4c6f81defa..7dfd099df1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 99b9de3fe2..9d0e1790f0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From a6b6644c2e441513bfba493fbe56e307996bd4ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 16:22:13 +0900 Subject: [PATCH 51/79] Replace LINQ queries with recommendations --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index e61720d8b9..70b7d1f740 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -96,12 +96,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } // we should only be checking the bonus/progress after the spinner has fully completed. - if (!results.OfType().Any(r => r.TimeCompleted != null)) + if (results.OfType().All(r => r.TimeCompleted == null)) return false; return results.Any(r => r.Type == HitResult.SmallBonus) - && !results.Any(r => r.Type == HitResult.LargeBonus); + && results.All(r => r.Type != HitResult.LargeBonus); } }); } From 054ed546e3800e6aa0425b362b68dacf9e19fb75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 16:56:50 +0900 Subject: [PATCH 52/79] Fix intermittent failures in remaining test method --- .../Mods/TestSceneOsuModSpunOut.cs | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 70b7d1f740..a8953c1a6f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -26,13 +26,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods protected override bool AllowFail => true; [Test] - public void TestSpinnerAutoCompleted() => CreateModTest(new ModTestData + public void TestSpinnerAutoCompleted() { - Mod = new OsuModSpunOut(), - Autoplay = false, - Beatmap = singleSpinnerBeatmap, - PassCondition = () => Player.ChildrenOfType().SingleOrDefault()?.Progress >= 1 - }); + DrawableSpinner spinner = null; + JudgementResult lastResult = null; + + CreateModTest(new ModTestData + { + Mod = new OsuModSpunOut(), + Autoplay = false, + Beatmap = singleSpinnerBeatmap, + PassCondition = () => + { + // Bind to the first spinner's results for further tracking. + if (spinner == null) + { + // We only care about the first spinner we encounter for this test. + var nextSpinner = Player.ChildrenOfType().SingleOrDefault(); + + if (nextSpinner == null) + return false; + + lastResult = null; + + spinner = nextSpinner; + spinner.OnNewResult += (o, result) => lastResult = result; + } + + return lastResult?.Type == HitResult.Great; + } + }); + } [TestCase(null)] [TestCase(typeof(OsuModDoubleTime))] From 5d73691de4e13ed827d0a731ea909e7b33d115d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:02:39 +0900 Subject: [PATCH 53/79] Use existing `HandleLink` flow rather than reimplmenting --- osu.Game/OsuGame.cs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 77eec004c0..fa5a336b7c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -906,7 +906,7 @@ namespace osu.Game if (firstPath.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) { - handleOsuProtocolUrl(firstPath); + HandleLink(firstPath); } else { @@ -916,30 +916,6 @@ namespace osu.Game } } - private void handleOsuProtocolUrl(string url) - { - if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) - throw new ArgumentException("Invalid osu URL provided.", nameof(url)); - - // note that `StringSplitOptions.RemoveEmptyEntries` is not explicitly specified here - // in order to ensure that the protocol URL is valid (i.e. it has two slashes in the `osu://` part, - // causing the link content to be stored to the 2nd index rather than the 1st). - string[] pieces = url.Split('/'); - - switch (pieces[2]) - { - case "s": - if (int.TryParse(pieces[3], out int beatmapSetId)) - ShowBeatmapSet(beatmapSetId); - break; - - case "b": - if (int.TryParse(pieces[3], out int beatmapId)) - ShowBeatmap(beatmapId); - break; - } - } - private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) { otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); From 28c9c5ab6a0ac889223a7f46ba6b16504cc9a3ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:05:46 +0900 Subject: [PATCH 54/79] Remove unnecessary `ShouldSerialize` rules in `Room` --- osu.Game/Online/Rooms/Room.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 8ed37ad6b5..7512d4fcf9 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -230,15 +230,9 @@ namespace osu.Game.Online.Rooms // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed // unless the fields are also renamed. - [UsedImplicitly] - public bool ShouldSerializeRoomID() => false; - [UsedImplicitly] public bool ShouldSerializeHost() => false; - [UsedImplicitly] - public bool ShouldSerializeEndDate() => false; - #endregion [JsonObject(MemberSerialization.OptIn)] From f14a9af801c53552356f798a74e68fb6a18b4a3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:06:40 +0900 Subject: [PATCH 55/79] Make `Room` opt-in rather than opt-out for json serialization --- osu.Game/Online/Rooms/Room.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 7512d4fcf9..5deedaaa6b 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -15,6 +15,7 @@ using osu.Game.Utils; namespace osu.Game.Online.Rooms { + [JsonObject(MemberSerialization.OptIn)] public class Room : IDeepCloneable { [Cached] @@ -49,7 +50,6 @@ namespace osu.Game.Online.Rooms public readonly Bindable DifficultyRange = new Bindable(); [Cached] - [JsonIgnore] public readonly Bindable Category = new Bindable(); // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) @@ -62,19 +62,15 @@ namespace osu.Game.Online.Rooms } [Cached] - [JsonIgnore] public readonly Bindable MaxAttempts = new Bindable(); [Cached] - [JsonIgnore] public readonly Bindable Status = new Bindable(new RoomStatusOpen()); [Cached] - [JsonIgnore] public readonly Bindable Availability = new Bindable(); [Cached] - [JsonIgnore] public readonly Bindable Type = new Bindable(); // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) @@ -87,7 +83,6 @@ namespace osu.Game.Online.Rooms } [Cached] - [JsonIgnore] public readonly Bindable QueueMode = new Bindable(); [JsonConverter(typeof(SnakeCaseStringEnumConverter))] @@ -99,7 +94,6 @@ namespace osu.Game.Online.Rooms } [Cached] - [JsonIgnore] public readonly Bindable MaxParticipants = new Bindable(); [Cached] @@ -124,7 +118,6 @@ namespace osu.Game.Online.Rooms public readonly Bindable Password = new Bindable(); [Cached] - [JsonIgnore] public readonly Bindable Duration = new Bindable(); [JsonProperty("duration")] From 43c83d2de1cd2cb861ba57f80dc903c837cb8181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:07:29 +0900 Subject: [PATCH 56/79] Add note about why `RoomID` is nulled in `DeepClone` --- osu.Game/Online/Rooms/Room.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 5deedaaa6b..b301c81921 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -162,6 +162,8 @@ namespace osu.Game.Online.Rooms var copy = new Room(); copy.CopyFrom(this); + + // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. copy.RoomID.Value = null; return copy; From 53bbd0067545fbfc99e131de0a1bb6918980ed4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:12:38 +0900 Subject: [PATCH 57/79] Also make `APIUser` opt-in and remove the remaining serialization exclusion rule --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 1 + osu.Game/Online/Rooms/Room.cs | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 2b64e5de06..a53ac1cd9b 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -12,6 +12,7 @@ using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { + [JsonObject(MemberSerialization.OptIn)] public class APIUser : IEquatable, IUser { [JsonProperty(@"id")] diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b301c81921..c7f34905e2 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -219,17 +218,6 @@ namespace osu.Game.Online.Rooms Playlist.RemoveAll(i => i.Expired); } - #region Newtonsoft.Json implicit ShouldSerialize() methods - - // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases. - // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed - // unless the fields are also renamed. - - [UsedImplicitly] - public bool ShouldSerializeHost() => false; - - #endregion - [JsonObject(MemberSerialization.OptIn)] public class RoomPlaylistItemStats { From 5dd0d48df968594b86f4463fc1634dc9c93abbe0 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 23 Feb 2022 14:06:22 +0100 Subject: [PATCH 58/79] Move the key handling logic to MainMenu and simplify it Also makes use of the host.SuspendToBackground() return value. --- osu.Game/Screens/Menu/ExitConfirmOverlay.cs | 23 -------- osu.Game/Screens/Menu/MainMenu.cs | 59 +++++++++++---------- 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index 253ec17c28..a90b83c5fe 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -3,7 +3,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -43,26 +42,4 @@ namespace osu.Game.Screens.Menu } } } - - /// - /// An that behaves as if the is always 0. - /// - /// This is useful for mobile devices using gesture navigation, where holding to confirm is not possible. - public class NoHoldExitConfirmOverlay : ExitConfirmOverlay, IKeyBindingHandler - { - public new bool OnPressed(KeyBindingPressEvent e) - { - if (e.Repeat) - return false; - - if (e.Action == GlobalAction.Back) - { - Progress.Value = 1; - Confirm(); - return true; - } - - return false; - } - } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 7bc0cb48bf..a2cb448d40 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -6,12 +6,15 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; @@ -26,7 +29,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Menu { - public class MainMenu : OsuScreen, IHandlePresentBeatmap + public class MainMenu : OsuScreen, IHandlePresentBeatmap, IKeyBindingHandler { public const float FADE_IN_DURATION = 300; @@ -90,14 +93,6 @@ namespace osu.Game.Screens.Menu } }); } - else if (host.CanSuspendToBackground) - { - AddInternal(exitConfirmOverlay = new NoHoldExitConfirmOverlay - { - // treat as if the UIHoldActivationDelay is always 0. see NoHoldExitConfirmOverlay xmldoc for more info. - Action = this.Exit - }); - } AddRangeInternal(new[] { @@ -157,24 +152,6 @@ namespace osu.Game.Screens.Menu private void confirmAndExit() { - if (host.CanSuspendToBackground) - { - // cancel the overlay as we're not actually exiting. - // this is the same action as 'onCancel' in `ConfirmExitDialog`. - exitConfirmOverlay.Abort(); - - // fade the track so the Bass.Pause() on suspend isn't as jarring. - const double fade_time = 500; - musicController.CurrentTrack - .VolumeTo(0, fade_time, Easing.Out).Then() - .VolumeTo(1, fade_time, Easing.In); - - host.SuspendToBackground(); - - // on hosts that can only suspend, we don't ever want to exit the game. - return; - } - if (exitConfirmed) return; exitConfirmed = true; @@ -324,5 +301,33 @@ namespace osu.Game.Screens.Menu Schedule(loadSoloSongSelect); } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + if (e.Action == GlobalAction.Back && host.CanSuspendToBackground) + { + bool didSuspend = host.SuspendToBackground(); + + if (didSuspend) + { + // fade the track so the Bass.Pause() on suspend isn't as jarring. + const double fade_time = 500; + musicController.CurrentTrack + .VolumeTo(0, fade_time, Easing.Out).Then() + .VolumeTo(1, fade_time, Easing.In); + + return true; + } + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } From 8a0aba6c596bcadf1c0e15ef661e8846503841d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Feb 2022 23:11:12 +0100 Subject: [PATCH 59/79] Implement mod panel for new mod select screen --- .../Visual/UserInterface/TestSceneModPanel.cs | 42 +++ osu.Game/Overlays/Mods/ModPanel.cs | 272 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs create mode 100644 osu.Game/Overlays/Mods/ModPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs new file mode 100644 index 0000000000..bdf3f98863 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModPanel : OsuManualInputManagerTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestVariousPanels() + { + AddStep("create content", () => Child = new FillFlowContainer + { + Width = 300, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(0, 5), + Children = new[] + { + new ModPanel(new OsuModHalfTime()), + new ModPanel(new OsuModFlashlight()), + new ModPanel(new OsuModAutoplay()), + new ModPanel(new OsuModAlternate()), + new ModPanel(new OsuModApproachDifferent()) + } + }); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs new file mode 100644 index 0000000000..4d08b22017 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -0,0 +1,272 @@ +// 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.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; +using osuTK.Input; + +#nullable enable + +namespace osu.Game.Overlays.Mods +{ + public class ModPanel : OsuClickableContainer + { + public Mod Mod { get; } + public BindableBool Active { get; } = new BindableBool(); + + protected readonly Box Background; + protected readonly Container SwitchContainer; + protected readonly Container MainContentContainer; + protected readonly Box TextBackground; + protected readonly FillFlowContainer TextFlow; + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } = null!; + + protected const double TRANSITION_DURATION = 150; + protected const float SHEAR_X = 0.2f; + + protected const float HEIGHT = 42; + protected const float CORNER_RADIUS = 7; + protected const float IDLE_SWITCH_WIDTH = 54; + protected const float EXPANDED_SWITCH_WIDTH = 70; + + private Colour4 activeColour; + private Colour4 activeHoverColour; + + private Sample? sampleOff; + private Sample? sampleOn; + + public ModPanel(Mod mod) + { + Mod = mod; + + RelativeSizeAxes = Axes.X; + Height = 42; + + // all below properties are applied to `Content` rather than the `ModPanel` in its entirety + // to allow external components to set these properties on the panel without affecting + // its "internal" appearance. + Content.Masking = true; + Content.CornerRadius = CORNER_RADIUS; + Content.BorderThickness = 2; + Content.Shear = new Vector2(SHEAR_X, 0); + + Children = new Drawable[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + SwitchContainer = new Container + { + RelativeSizeAxes = Axes.Y, + Child = new ModSwitchSmall(mod) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Active = { BindTarget = Active }, + Shear = new Vector2(-SHEAR_X, 0), + Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) + } + }, + MainContentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = CORNER_RADIUS, + Children = new Drawable[] + { + TextBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + TextFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 17.5f, + Vertical = 4 + }, + Direction = FillDirection.Vertical, + Children = new[] + { + new OsuSpriteText + { + Text = mod.Name, + Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), + Shear = new Vector2(-SHEAR_X, 0), + Margin = new MarginPadding + { + Left = -18 * SHEAR_X + } + }, + new OsuSpriteText + { + Text = mod.Description, + Font = OsuFont.Default.With(size: 12), + RelativeSizeAxes = Axes.X, + Truncate = true, + Shear = new Vector2(-SHEAR_X, 0) + } + } + } + } + } + } + }; + + Action = Active.Toggle; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + sampleOn = audio.Samples.Get(@"UI/check-on"); + sampleOff = audio.Samples.Get(@"UI/check-off"); + + activeColour = colours.ForModType(Mod.Type); + activeHoverColour = activeColour.Lighten(0.3f); + } + + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); + + protected override void LoadComplete() + { + base.LoadComplete(); + Active.BindValueChanged(_ => + { + playStateChangeSamples(); + UpdateState(); + }); + + UpdateState(); + FinishTransforms(true); + } + + private void playStateChangeSamples() + { + if (Active.Value) + sampleOn?.Play(); + else + sampleOff?.Play(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + UpdateState(); + base.OnHoverLost(e); + } + + private double? mouseDownTime; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Left) + mouseDownTime = Time.Current; + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + mouseDownTime = null; + base.OnMouseUp(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + mouseDownTime = null; + return true; + } + + protected override void Update() + { + base.Update(); + + if (mouseDownTime != null) + { + double startTime = mouseDownTime.Value; + double endTime = startTime + 600; + + float startValue = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; + float endValue = IDLE_SWITCH_WIDTH + (EXPANDED_SWITCH_WIDTH - IDLE_SWITCH_WIDTH) * (Active.Value ? 0.2f : 0.8f); + + float targetWidth = Interpolation.ValueAt(Math.Clamp(Time.Current, startTime, endTime), startValue, endValue, startTime, endTime, Easing.OutQuint); + + SwitchContainer.Width = targetWidth; + MainContentContainer.Padding = new MarginPadding + { + Left = targetWidth, + Right = CORNER_RADIUS + }; + } + } + + protected virtual void UpdateState() + { + if (Active.Value) + { + Colour4 backgroundTextColour = IsHovered ? activeHoverColour : activeColour; + Colour4 backgroundColour = Interpolation.ValueAt(0.7f, Colour4.Black, backgroundTextColour, 0, 1); + + Content.TransformTo(nameof(BorderColour), (ColourInfo)backgroundColour, TRANSITION_DURATION, Easing.OutQuint); + Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(EXPANDED_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = EXPANDED_SWITCH_WIDTH, + Right = CORNER_RADIUS + }, TRANSITION_DURATION, Easing.OutQuint); + TextBackground.FadeColour(backgroundTextColour, TRANSITION_DURATION, Easing.OutQuint); + TextFlow.FadeColour(ColourProvider.Background6, TRANSITION_DURATION, Easing.OutQuint); + } + else + { + Colour4 backgroundColour = ColourProvider.Background3; + if (IsHovered) + backgroundColour = Interpolation.ValueAt(0.25f, backgroundColour, activeColour, 0, 1); + + Colour4 textBackgroundColour = ColourProvider.Background2; + if (IsHovered) + textBackgroundColour = Interpolation.ValueAt(0.25f, textBackgroundColour, activeColour, 0, 1); + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, textBackgroundColour), TRANSITION_DURATION, Easing.OutQuint); + Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(IDLE_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = IDLE_SWITCH_WIDTH, + Right = CORNER_RADIUS + }, TRANSITION_DURATION, Easing.OutQuint); + TextBackground.FadeColour(textBackgroundColour, TRANSITION_DURATION, Easing.OutQuint); + TextFlow.FadeColour(Colour4.White, TRANSITION_DURATION, Easing.OutQuint); + } + } + } +} From bbe2dfa458c5c0a0dfa56d68717c8c625c4fd01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Feb 2022 23:11:26 +0100 Subject: [PATCH 60/79] Move out incompatibility displaying tooltip to own class --- .../IncompatibilityDisplayingModButton.cs | 51 --------------- .../Mods/IncompatibilityDisplayingTooltip.cs | 64 +++++++++++++++++++ 2 files changed, 64 insertions(+), 51 deletions(-) create mode 100644 osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs index 0f51439252..6e2cb40596 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs @@ -8,11 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play.HUD; using osu.Game.Utils; using osuTK; @@ -66,52 +62,5 @@ namespace osu.Game.Overlays.Mods } public override ITooltip GetCustomTooltip() => new IncompatibilityDisplayingTooltip(); - - private class IncompatibilityDisplayingTooltip : ModButtonTooltip - { - private readonly OsuSpriteText incompatibleText; - - private readonly Bindable> incompatibleMods = new Bindable>(); - - [Resolved] - private Bindable ruleset { get; set; } - - public IncompatibilityDisplayingTooltip() - { - AddRange(new Drawable[] - { - incompatibleText = new OsuSpriteText - { - Margin = new MarginPadding { Top = 5 }, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = "Incompatible with:" - }, - new ModDisplay - { - Current = incompatibleMods, - ExpansionMode = ExpansionMode.AlwaysExpanded, - Scale = new Vector2(0.7f) - } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - incompatibleText.Colour = colours.BlueLight; - } - - protected override void UpdateDisplay(Mod mod) - { - base.UpdateDisplay(mod); - - var incompatibleTypes = mod.IncompatibleMods; - - var allMods = ruleset.Value.CreateInstance().AllMods; - - incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList(); - incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; - } - } } } diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs new file mode 100644 index 0000000000..d8117c8f00 --- /dev/null +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + internal class IncompatibilityDisplayingTooltip : ModButtonTooltip + { + private readonly OsuSpriteText incompatibleText; + + private readonly Bindable> incompatibleMods = new Bindable>(); + + [Resolved] + private Bindable ruleset { get; set; } + + public IncompatibilityDisplayingTooltip() + { + AddRange(new Drawable[] + { + incompatibleText = new OsuSpriteText + { + Margin = new MarginPadding { Top = 5 }, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = "Incompatible with:" + }, + new ModDisplay + { + Current = incompatibleMods, + ExpansionMode = ExpansionMode.AlwaysExpanded, + Scale = new Vector2(0.7f) + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + incompatibleText.Colour = colours.BlueLight; + } + + protected override void UpdateDisplay(Mod mod) + { + base.UpdateDisplay(mod); + + var incompatibleTypes = mod.IncompatibleMods; + + var allMods = ruleset.Value.CreateInstance().AllMods; + + incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList(); + incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; + } + } +} From 713f89a59c7d0c964d5c4f1c0760dd45fe596bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Feb 2022 23:11:38 +0100 Subject: [PATCH 61/79] Implement incompatibility-displaying variant of mod panel --- .../Visual/UserInterface/TestSceneModPanel.cs | 39 ++++++++ .../Mods/IncompatibilityDisplayingModPanel.cs | 88 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs index bdf3f98863..95323e5dfa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs @@ -1,14 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { @@ -38,5 +41,41 @@ namespace osu.Game.Tests.Visual.UserInterface } }); } + + [Test] + public void TestIncompatibilityDisplay() + { + IncompatibilityDisplayingModPanel panel = null; + + AddStep("create panel with DT", () => Child = panel = new IncompatibilityDisplayingModPanel(new OsuModDoubleTime()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = 300 + }); + + clickPanel(); + AddAssert("panel active", () => panel.Active.Value); + + clickPanel(); + AddAssert("panel not active", () => !panel.Active.Value); + + AddStep("set incompatible mod", () => SelectedMods.Value = new[] { new OsuModHalfTime() }); + + clickPanel(); + AddAssert("panel not active", () => !panel.Active.Value); + + AddStep("reset mods", () => SelectedMods.Value = Array.Empty()); + + clickPanel(); + AddAssert("panel active", () => panel.Active.Value); + + void clickPanel() => AddStep("click panel", () => + { + InputManager.MoveMouseTo(panel); + InputManager.Click(MouseButton.Left); + }); + } } } diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs new file mode 100644 index 0000000000..4b6759c209 --- /dev/null +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs @@ -0,0 +1,88 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; + +namespace osu.Game.Overlays.Mods +{ + public class IncompatibilityDisplayingModPanel : ModPanel, IHasCustomTooltip + { + private readonly BindableBool incompatible = new BindableBool(); + + [Resolved] + private Bindable> selectedMods { get; set; } + + public IncompatibilityDisplayingModPanel(Mod mod) + : base(mod) + { + } + + protected override void LoadComplete() + { + selectedMods.BindValueChanged(_ => updateIncompatibility(), true); + incompatible.BindValueChanged(_ => Scheduler.AddOnce(UpdateState)); + // base call will run `UpdateState()` first time and finish transforms. + base.LoadComplete(); + } + + private void updateIncompatibility() + { + incompatible.Value = selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(Mod) && !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(Mod)); + } + + protected override void UpdateState() + { + Action = incompatible.Value ? () => { } : (Action)Active.Toggle; + + if (incompatible.Value) + { + Colour4 backgroundColour = ColourProvider.Background5; + Colour4 textBackgroundColour = ColourProvider.Background4; + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, textBackgroundColour), TRANSITION_DURATION, Easing.OutQuint); + Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); + + SwitchContainer.ResizeWidthTo(IDLE_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); + SwitchContainer.FadeColour(Colour4.Gray, TRANSITION_DURATION, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = IDLE_SWITCH_WIDTH, + Right = CORNER_RADIUS + }, TRANSITION_DURATION, Easing.OutQuint); + + TextBackground.FadeColour(textBackgroundColour, TRANSITION_DURATION, Easing.OutQuint); + TextFlow.FadeColour(Colour4.White.Opacity(0.5f), TRANSITION_DURATION, Easing.OutQuint); + return; + } + + SwitchContainer.FadeColour(Colour4.White, TRANSITION_DURATION, Easing.OutQuint); + base.UpdateState(); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (incompatible.Value) + return true; // bypasses base call purposely in order to not play out the intermediate state animation. + + return base.OnMouseDown(e); + } + + #region IHasCustomTooltip + + public ITooltip GetCustomTooltip() => new IncompatibilityDisplayingTooltip(); + + public Mod TooltipContent => Mod; + + #endregion + } +} From 435bdd0b4a68d7abf66b2f7eb0c5d70f16246580 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 14:55:35 +0900 Subject: [PATCH 62/79] Combine and simplify state management logic This makes a few changes to bring things into a better shape during mouse interactions with the mod panels: - Dragging away from the panel now works in line with other buttons (ie. `OsuButton`) - Hovering now uses a lightened version of the current state, rather than always using the active colour. I think this feels better. - Mouse down now uses a transform point of 0.5. This is to give the button a feeling of one of those latching light switches which resists until reaching a point of overcoming the spring and switching state. I think 0.4 (non-active) and 0.6 (from active) may work better, but left at 0.5 for simplicity of implementation and I think it's good enough? - Border always uses the gradiented version. I did this for simplicity of implementation, but also think it looks better. - Adjusted transform durations to feel better to me. --- osu.Game/Overlays/Mods/ModPanel.cs | 102 ++++++++++------------------- 1 file changed, 35 insertions(+), 67 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 4d08b22017..13e2b5bb0b 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -40,6 +40,7 @@ namespace osu.Game.Overlays.Mods protected OverlayColourProvider ColourProvider { get; private set; } = null!; protected const double TRANSITION_DURATION = 150; + protected const float SHEAR_X = 0.2f; protected const float HEIGHT = 42; @@ -48,7 +49,6 @@ namespace osu.Game.Overlays.Mods protected const float EXPANDED_SWITCH_WIDTH = 70; private Colour4 activeColour; - private Colour4 activeHoverColour; private Sample? sampleOff; private Sample? sampleOn; @@ -146,7 +146,6 @@ namespace osu.Game.Overlays.Mods sampleOff = audio.Samples.Get(@"UI/check-off"); activeColour = colours.ForModType(Mod.Type); - activeHoverColour = activeColour.Lighten(0.3f); } protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); @@ -184,89 +183,58 @@ namespace osu.Game.Overlays.Mods base.OnHoverLost(e); } - private double? mouseDownTime; + private bool mouseDown; protected override bool OnMouseDown(MouseDownEvent e) { if (e.Button == MouseButton.Left) - mouseDownTime = Time.Current; + mouseDown = true; + + UpdateState(); return true; } protected override void OnMouseUp(MouseUpEvent e) { - mouseDownTime = null; + mouseDown = false; + + UpdateState(); base.OnMouseUp(e); } - protected override bool OnDragStart(DragStartEvent e) - { - mouseDownTime = null; - return true; - } - - protected override void Update() - { - base.Update(); - - if (mouseDownTime != null) - { - double startTime = mouseDownTime.Value; - double endTime = startTime + 600; - - float startValue = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; - float endValue = IDLE_SWITCH_WIDTH + (EXPANDED_SWITCH_WIDTH - IDLE_SWITCH_WIDTH) * (Active.Value ? 0.2f : 0.8f); - - float targetWidth = Interpolation.ValueAt(Math.Clamp(Time.Current, startTime, endTime), startValue, endValue, startTime, endTime, Easing.OutQuint); - - SwitchContainer.Width = targetWidth; - MainContentContainer.Padding = new MarginPadding - { - Left = targetWidth, - Right = CORNER_RADIUS - }; - } - } - protected virtual void UpdateState() { - if (Active.Value) + float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; + double transitionDuration = TRANSITION_DURATION; + + Colour4 textBackgroundColour = Active.Value ? activeColour : (Colour4)ColourProvider.Background2; + Colour4 mainBackgroundColour = Active.Value ? activeColour.Darken(0.3f) : (Colour4)ColourProvider.Background3; + Colour4 textColour = Active.Value ? (Colour4)ColourProvider.Background6 : Colour4.White; + + // Hover affects colour of button background + if (IsHovered) { - Colour4 backgroundTextColour = IsHovered ? activeHoverColour : activeColour; - Colour4 backgroundColour = Interpolation.ValueAt(0.7f, Colour4.Black, backgroundTextColour, 0, 1); - - Content.TransformTo(nameof(BorderColour), (ColourInfo)backgroundColour, TRANSITION_DURATION, Easing.OutQuint); - Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); - SwitchContainer.ResizeWidthTo(EXPANDED_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); - MainContentContainer.TransformTo(nameof(Padding), new MarginPadding - { - Left = EXPANDED_SWITCH_WIDTH, - Right = CORNER_RADIUS - }, TRANSITION_DURATION, Easing.OutQuint); - TextBackground.FadeColour(backgroundTextColour, TRANSITION_DURATION, Easing.OutQuint); - TextFlow.FadeColour(ColourProvider.Background6, TRANSITION_DURATION, Easing.OutQuint); + textBackgroundColour = textBackgroundColour.Lighten(0.1f); + mainBackgroundColour = mainBackgroundColour.Lighten(0.1f); } - else + + // Mouse down adds a halfway tween of the movement + if (mouseDown) { - Colour4 backgroundColour = ColourProvider.Background3; - if (IsHovered) - backgroundColour = Interpolation.ValueAt(0.25f, backgroundColour, activeColour, 0, 1); - - Colour4 textBackgroundColour = ColourProvider.Background2; - if (IsHovered) - textBackgroundColour = Interpolation.ValueAt(0.25f, textBackgroundColour, activeColour, 0, 1); - - Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, textBackgroundColour), TRANSITION_DURATION, Easing.OutQuint); - Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); - SwitchContainer.ResizeWidthTo(IDLE_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); - MainContentContainer.TransformTo(nameof(Padding), new MarginPadding - { - Left = IDLE_SWITCH_WIDTH, - Right = CORNER_RADIUS - }, TRANSITION_DURATION, Easing.OutQuint); - TextBackground.FadeColour(textBackgroundColour, TRANSITION_DURATION, Easing.OutQuint); - TextFlow.FadeColour(Colour4.White, TRANSITION_DURATION, Easing.OutQuint); + targetWidth = (float)Interpolation.Lerp(IDLE_SWITCH_WIDTH, EXPANDED_SWITCH_WIDTH, 0.5f); + transitionDuration *= 4; } + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(mainBackgroundColour, textBackgroundColour), transitionDuration, Easing.OutQuint); + Background.FadeColour(mainBackgroundColour, transitionDuration, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = targetWidth, + Right = CORNER_RADIUS + }, transitionDuration, Easing.OutQuint); + TextBackground.FadeColour(textBackgroundColour, transitionDuration, Easing.OutQuint); + TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } } } From 3f6bdc5585de6653642d8c6ec119e6917dcb67d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 15:40:06 +0900 Subject: [PATCH 63/79] Don't expose "mark as read" errors to the user via notifications This can happen if the user leaves the channel before the request is fired. You can't mark a channel as read when you're not in the channel. Addresses https://github.com/ppy/osu/discussions/16973. --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 77b52c34d9..38e655db71 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -618,7 +618,7 @@ namespace osu.Game.Online.Chat var req = new MarkChannelAsReadRequest(channel, message); req.Success += () => channel.LastReadId = message.Id; - req.Failure += e => Logger.Error(e, $"Failed to mark channel {channel} up to '{message}' as read"); + req.Failure += e => Logger.Log($"Failed to mark channel {channel} up to '{message}' as read ({e.Message})", LoggingTarget.Network, LogLevel.Verbose); api.Queue(req); } From 401cf2a955eb82d45afa03105090df7ecf6931eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 15:54:45 +0900 Subject: [PATCH 64/79] Allow pausing game-wide audio via hotkey as long as `LocalUserPlaying` is not set `Player` seems to handle this correctly locally already, which is to say if the user attempts to toggle the pause state incorrectly, it will still recover. The logic stoppic this operation was only in the key binding handler, which meant it was already possible from the now playing overlay this whole time, so I *think* this should be quite safe. --- osu.Game/Overlays/Music/MusicKeyBindingHandler.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index baee17fb00..6b33c9200e 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -30,17 +30,20 @@ namespace osu.Game.Overlays.Music [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } + [Resolved] + private OsuGame game { get; set; } + public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat) return false; - if (beatmap.Disabled) - return false; - switch (e.Action) { case GlobalAction.MusicPlay: + if (game.LocalUserPlaying.Value) + return false; + // use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842) bool wasPlaying = musicController.IsPlaying; @@ -49,11 +52,17 @@ namespace osu.Game.Overlays.Music return true; case GlobalAction.MusicNext: + if (beatmap.Disabled) + return false; + musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast(GlobalActionKeyBindingStrings.MusicNext, e.Action))); return true; case GlobalAction.MusicPrev: + if (beatmap.Disabled) + return false; + musicController.PreviousTrack(res => { switch (res) From b4a54b38e71a960bcb6382aadc32f1cbe2ade358 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 16:02:16 +0900 Subject: [PATCH 65/79] Remove redundant parameter specification --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 38e655db71..47e45e67d1 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -618,7 +618,7 @@ namespace osu.Game.Online.Chat var req = new MarkChannelAsReadRequest(channel, message); req.Success += () => channel.LastReadId = message.Id; - req.Failure += e => Logger.Log($"Failed to mark channel {channel} up to '{message}' as read ({e.Message})", LoggingTarget.Network, LogLevel.Verbose); + req.Failure += e => Logger.Log($"Failed to mark channel {channel} up to '{message}' as read ({e.Message})", LoggingTarget.Network); api.Queue(req); } From c6d78b93257b172d044cc8932bd857555a82571d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 16:12:15 +0900 Subject: [PATCH 66/79] Fix several oversights in data linking causing drawable rooms not updating as expected --- osu.Game/Online/Rooms/Room.cs | 2 ++ .../Components/StarRatingRangeDisplay.cs | 16 ++++++---------- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 +++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index c7f34905e2..a33150fe08 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -46,6 +46,7 @@ namespace osu.Game.Online.Rooms public readonly Bindable PlaylistItemStats = new Bindable(); [JsonProperty("difficulty_range")] + [Cached] public readonly Bindable DifficultyRange = new Bindable(); [Cached] @@ -190,6 +191,7 @@ namespace osu.Game.Online.Rooms QueueMode.Value = other.QueueMode.Value; DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; + CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value; if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 0b673006ef..7425e46bd3 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -12,7 +11,6 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Components @@ -72,25 +70,23 @@ namespace osu.Game.Screens.OnlinePlay.Components }; } - [Resolved] - private Room room { get; set; } - protected override void LoadComplete() { base.LoadComplete(); - Playlist.BindCollectionChanged(updateRange, true); + DifficultyRange.BindValueChanged(_ => updateRange()); + Playlist.BindCollectionChanged((_, __) => updateRange(), true); } - private void updateRange(object sender, NotifyCollectionChangedEventArgs e) + private void updateRange() { StarDifficulty minDifficulty; StarDifficulty maxDifficulty; - if (room.DifficultyRange.Value != null) + if (DifficultyRange.Value != null) { - minDifficulty = new StarDifficulty(room.DifficultyRange.Value.Min, 0); - maxDifficulty = new StarDifficulty(room.DifficultyRange.Value.Max, 0); + minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0); + maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0); } else { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 7d1feb0316..984880dc3c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } + [Resolved(typeof(Room))] + protected Bindable DifficultyRange { get; private set; } + [Resolved(typeof(Room))] protected Bindable Category { get; private set; } From bb1aa032bdd70c62abb6d6fe3833dacde8b32a71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 16:20:40 +0900 Subject: [PATCH 67/79] Combine `SelectedItem` and `CurrentPlaylistItem` into same storage --- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 4 ++-- .../Match/MultiplayerReadyButton.cs | 4 ++-- .../Match/Playlist/MultiplayerPlaylist.cs | 4 ++-- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 22 ++++++++----------- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index a1a82c907a..5adce862a0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -388,7 +388,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override void LoadComplete() { base.LoadComplete(); - SelectedItem.BindValueChanged(onSelectedItemChanged, true); + CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true); } private CancellationTokenSource beatmapLookupCancellation; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 7f1db733b3..be98a9d4e9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -343,7 +343,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match base.LoadComplete(); drawablePlaylist.Items.BindTo(Playlist); - drawablePlaylist.SelectedItem.BindTo(SelectedItem); + drawablePlaylist.SelectedItem.BindTo(CurrentPlaylistItem); } protected override void Update() @@ -419,7 +419,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (text.StartsWith(not_found_prefix, StringComparison.Ordinal)) { ErrorText.Text = "The selected beatmap is not available online."; - SelectedItem.Value.MarkInvalid(); + CurrentPlaylistItem.Value.MarkInvalid(); } else { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 06959d942f..023af85f3b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - SelectedItem.BindValueChanged(_ => updateState()); + CurrentPlaylistItem.BindValueChanged(_ => updateState()); } protected override void OnRoomUpdated() @@ -111,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match bool enableButton = Room?.State == MultiplayerRoomState.Open - && SelectedItem.Value?.ID == Room.Settings.PlaylistItemId + && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 7b90532cce..eeafebfec0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -52,14 +52,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem }, + SelectedItem = { BindTarget = CurrentPlaylistItem }, RequestEdit = item => RequestEdit?.Invoke(item) }, historyList = new MultiplayerHistoryList { RelativeSizeAxes = Axes.Both, Alpha = 0, - SelectedItem = { BindTarget = SelectedItem } + SelectedItem = { BindTarget = CurrentPlaylistItem } } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 984880dc3c..95d9b2af15 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -32,6 +32,10 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Type { get; private set; } + /// + /// The currently selected item in the , or the current item from + /// if this is not within a . + /// [Resolved(typeof(Room))] protected Bindable CurrentPlaylistItem { get; private set; } @@ -80,12 +84,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(CanBeNull = true)] private IBindable subScreenSelectedItem { get; set; } - /// - /// The currently selected item in the , or the current item from - /// if this is not within a . - /// - protected readonly Bindable SelectedItem = new Bindable(); - protected override void LoadComplete() { base.LoadComplete(); @@ -96,13 +94,11 @@ namespace osu.Game.Screens.OnlinePlay protected void UpdateSelectedItem() { - if (RoomID.Value == null || subScreenSelectedItem == null) - { - SelectedItem.Value = CurrentPlaylistItem.Value ?? Playlist.GetCurrentItem(); - return; - } - - SelectedItem.Value = subScreenSelectedItem.Value; + // null room ID means this is a room in the process of being created. + if (RoomID.Value == null) + CurrentPlaylistItem.Value = Playlist.GetCurrentItem(); + else if (subScreenSelectedItem != null) + CurrentPlaylistItem.Value = subScreenSelectedItem.Value; } } } From 328166f0d56043199ce8e1de9e6af080dc22de35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 17:01:04 +0900 Subject: [PATCH 68/79] Add failing test --- osu.Game.Tests/Online/TestAPIModJsonSerialization.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 1b7a7656b5..d5ea3e492c 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -121,6 +121,17 @@ namespace osu.Game.Tests.Online Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2)); } + [Test] + public void TestAPIModDetachedFromSource() + { + var mod = new OsuModDoubleTime { SpeedChange = { Value = 1.01 } }; + var apiMod = new APIMod(mod); + + mod.SpeedChange.Value = 1.5; + + Assert.That(apiMod.Settings["speed_change"], Is.EqualTo(1.01d)); + } + private class TestRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) => new Mod[] From 2acaffd5e72b80bbb90ae0a0b2e250527db7365c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 17:01:11 +0900 Subject: [PATCH 69/79] Fix APIMod storing bindables instead of value --- osu.Game/Online/API/APIMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 62f9976c0f..67041cae07 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online.API var bindable = (IBindable)property.GetValue(mod); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), bindable); + Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable)); } } From 7193bc8554a0fb3db8cc2fd08fb31f3082fc91fa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 17:01:43 +0900 Subject: [PATCH 70/79] Fix playlists comparing mod equality via APIMod --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 8403e1e0fe..7efeae8129 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Screens; using osu.Game.Extensions; -using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -40,8 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists if (ruleset.Value.OnlineID != PlaylistItem.RulesetID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - var localMods = Mods.Value.Select(m => new APIMod(m)).ToArray(); - if (!PlaylistItem.RequiredMods.All(m => localMods.Any(m.Equals))) + var requiredLocalMods = PlaylistItem.RequiredMods.Select(m => m.ToMod(GameplayState.Ruleset)); + if (!requiredLocalMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); } From 255b3b067b3774b39a4f0cbf6f07555b5d3fbd2b Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 24 Feb 2022 10:13:27 +0100 Subject: [PATCH 71/79] Remove track fade --- osu.Game/Screens/Menu/MainMenu.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a2cb448d40..81e03b94ae 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -309,18 +309,8 @@ namespace osu.Game.Screens.Menu if (e.Action == GlobalAction.Back && host.CanSuspendToBackground) { - bool didSuspend = host.SuspendToBackground(); - - if (didSuspend) - { - // fade the track so the Bass.Pause() on suspend isn't as jarring. - const double fade_time = 500; - musicController.CurrentTrack - .VolumeTo(0, fade_time, Easing.Out).Then() - .VolumeTo(1, fade_time, Easing.In); - + if (host.SuspendToBackground()) return true; - } } return false; From 6f29cbccd140cd222d3c72e8c2b17c94080ae43c Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 24 Feb 2022 10:36:10 +0100 Subject: [PATCH 72/79] Remove unused using --- osu.Game/Screens/Menu/MainMenu.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 81e03b94ae..0357332cf6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; From c5b1e5cbf84d9c7f2ea52caa8b5e83b67c54b7d7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 20:11:58 +0900 Subject: [PATCH 73/79] Fix union resolver failing on multiple derived types --- osu.Game/Online/SignalRUnionWorkaroundResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index e64f9ed91c..c96f93df78 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -27,7 +27,7 @@ namespace osu.Game.Online // This should not be required. The fallback should work. But something is weird with the way caching is done. // For future adventurers, I would not advise looking into this further. It's likely not worth the effort. - baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType))); + baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType)).Distinct()); return new Dictionary(baseMap.Select(t => { From cd8190b2e744f635e86fe197cd6b360c9460963d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 20:45:15 +0900 Subject: [PATCH 74/79] Remove Microsoft.CodeAnalysis.NetAnalyzers package --- Directory.Build.props | 1 - 1 file changed, 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index c1682638c2..5bdf12218c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,6 @@ - $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset From c189cc5d002c0a14ab15b83cdc6c462c2d03f78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 24 Feb 2022 21:01:37 +0100 Subject: [PATCH 75/79] Remove unused using directive --- osu.Game/Overlays/Mods/ModPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 13e2b5bb0b..af8ad3eb18 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.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 osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; From 33a87976a8ac17f8ac0d559b254c6c4a0862c35e Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 24 Feb 2022 21:11:49 +0100 Subject: [PATCH 76/79] Rewrite to read better Co-authored-by: Dean Herbert --- osu.Game/Screens/Menu/MainMenu.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0357332cf6..b0208a0ae8 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -306,10 +307,13 @@ namespace osu.Game.Screens.Menu if (e.Repeat) return false; - if (e.Action == GlobalAction.Back && host.CanSuspendToBackground) + switch (e.Action) { - if (host.SuspendToBackground()) - return true; + case GlobalAction.Back: + // In the case of a host being able to exit, the back action is handled by ExitConfirmOverlay. + Debug.Assert(!host.CanExit); + + return host.SuspendToBackground(); } return false; From f9d9ad388b565a517bc2ad918368265a6a05fe56 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 25 Feb 2022 16:03:28 +0900 Subject: [PATCH 77/79] Add chat display to multiplayer spectator screen --- .../TestSceneMultiSpectatorScreen.cs | 7 ++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 27 +++++++++++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 7ce0c6a94d..77a06e5746 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -15,6 +15,7 @@ using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; +using osu.Game.Online.Rooms; using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; @@ -377,7 +378,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime)); + LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray(), gameplayStartTime)); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); @@ -465,8 +466,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { private readonly double? gameplayStartTime; - public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null) - : base(users) + public TestMultiSpectatorScreen(Room room, MultiplayerRoomUser[] users, double? gameplayStartTime = null) + : base(room, users) { this.gameplayStartTime = gameplayStartTime; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 429b0ad89b..c78dcb7cb6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -449,7 +449,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer switch (client.LocalUser.State) { case MultiplayerUserState.Spectating: - return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray()); + return new MultiSpectatorScreen(Room, users.Take(PlayerGrid.MAX_PLAYERS).ToArray()); default: return new MultiplayerPlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users)); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e5eeeb3448..1cc4497675 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -11,10 +11,12 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Online.Spectator; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Spectate; +using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { @@ -48,15 +50,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private PlayerArea currentAudioSource; private bool canStartMasterClock; + private readonly Room room; private readonly MultiplayerRoomUser[] users; /// /// Creates a new . /// + /// The room. /// The players to spectate. - public MultiSpectatorScreen(MultiplayerRoomUser[] users) + public MultiSpectatorScreen(Room room, MultiplayerRoomUser[] users) : base(users.Select(u => u.UserID).ToArray()) { + this.room = room; this.users = users; instances = new PlayerArea[Users.Count]; @@ -65,7 +70,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [BackgroundDependencyLoader] private void load() { - Container leaderboardContainer; + FillFlowContainer leaderboardFlow; Container scoreDisplayContainer; masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value); @@ -97,10 +102,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { new Drawable[] { - leaderboardContainer = new Container + leaderboardFlow = new FillFlowContainer { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5) }, grid = new PlayerGrid { RelativeSizeAxes = Axes.Both } } @@ -125,14 +133,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users) { Expanded = { Value = true }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, }, l => { foreach (var instance in instances) leaderboard.AddClock(instance.UserId, instance.GameplayClock); - leaderboardContainer.Add(leaderboard); + leaderboardFlow.Insert(0, leaderboard); if (leaderboard.TeamScores.Count == 2) { @@ -143,6 +149,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }, scoreDisplayContainer.Add); } }); + + LoadComponentAsync(new GameplayChatDisplay(room) + { + Expanded = { Value = true }, + }, chat => leaderboardFlow.Insert(1, chat)); } protected override void LoadComplete() From 48ed9c61442ca05bb7fda4eaf8d7c64f8a37cdad Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 25 Feb 2022 16:03:46 +0900 Subject: [PATCH 78/79] Enable high chat polling rate --- osu.Game/OsuGame.cs | 3 ++- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 3 +++ osu.Game/Users/UserActivity.cs | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fa5a336b7c..fb81e4fd14 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -838,7 +838,8 @@ namespace osu.Game channelManager.HighPollRate.Value = chatOverlay.State.Value == Visibility.Visible || API.Activity.Value is UserActivity.InLobby - || API.Activity.Value is UserActivity.InMultiplayerGame; + || API.Activity.Value is UserActivity.InMultiplayerGame + || API.Activity.Value is UserActivity.SpectatingMultiplayerGame; } Add(difficultyRecommender); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 1cc4497675..3bb76c4a76 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -16,6 +16,7 @@ using osu.Game.Online.Spectator; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Spectate; +using osu.Game.Users; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -36,6 +37,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); + protected override UserActivity InitialActivity => new UserActivity.SpectatingMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); + [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 516aa80652..2f945d6e1c 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -50,6 +50,16 @@ namespace osu.Game.Users public override string Status => $@"{base.Status} with others"; } + public class SpectatingMultiplayerGame : InGame + { + public SpectatingMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) + : base(beatmapInfo, ruleset) + { + } + + public override string Status => $"Watching others {base.Status.ToLowerInvariant()}"; + } + public class InPlaylistGame : InGame { public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) From 387ae59bc4108372afaa75f3050b10eb04ee26dc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 25 Feb 2022 16:12:25 +0900 Subject: [PATCH 79/79] Fix nullref in tests --- .../OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index 53fd111c27..d08a63e21f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,10 +16,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class GameplayChatDisplay : MatchChatDisplay, IKeyBindingHandler { - [Resolved] + [Resolved(CanBeNull = true)] + [CanBeNull] private ILocalUserPlayInfo localUserInfo { get; set; } - private IBindable localUserPlaying = new Bindable(); + private readonly IBindable localUserPlaying = new Bindable(); public override bool PropagatePositionalInputSubTree => !localUserPlaying.Value; @@ -46,7 +48,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); + if (localUserInfo != null) + localUserPlaying.BindTo(localUserInfo.IsPlaying); + localUserPlaying.BindValueChanged(playing => { // for now let's never hold focus. this avoid misdirected gameplay keys entering chat.