From f1535b74beb185c83f76d7e7e3a54e55b6c32a81 Mon Sep 17 00:00:00 2001 From: Kaleb Date: Sun, 13 Feb 2022 02:16:06 -0500 Subject: [PATCH 001/800] 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 002/800] 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 003/800] 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 004/800] 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 005/800] 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 006/800] 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 007/800] 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 008/800] 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 009/800] 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 010/800] 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 011/800] 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 012/800] 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 c124034cf3f671cc44b950851bb6a2f3900524cc Mon Sep 17 00:00:00 2001 From: dekrain Date: Wed, 16 Feb 2022 23:18:14 +0100 Subject: [PATCH 013/800] Add text displaying recent score time --- .../Online/Leaderboards/LeaderboardScore.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index c2393a5de5..f77982535d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -29,6 +30,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Online.Leaderboards { @@ -42,6 +44,7 @@ namespace osu.Game.Online.Leaderboards private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 35; + private const float date_width = 35; protected Container RankContainer { get; private set; } @@ -100,10 +103,18 @@ namespace osu.Game.Online.Leaderboards RelativeSizeAxes = Axes.Y, Width = rank_width, }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = date_width, + Child = new DateLabel(Score.Date), + }, content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_width, }, + Padding = new MarginPadding { Left = rank_width, Right = date_width, }, Children = new Drawable[] { new Container @@ -377,6 +388,36 @@ namespace osu.Game.Online.Leaderboards public LocalisableString TooltipText { get; } } + private class DateLabel : DrawableDate + { + public DateLabel(DateTimeOffset date) + : base(date, 20) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + protected override string Format() + { + var now = DateTime.Now; + var difference = now - Date; + + // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference.TotalMinutes < 1) + return CommonStrings.TimeNow.ToString(); + if (difference.TotalHours < 1) + return $@"{Math.Ceiling(difference.TotalMinutes)}min"; + if (difference.TotalDays < 1) + return $@"{Math.Ceiling(difference.TotalHours)}h"; + if (difference.TotalDays < 7) + return $@"{Math.Ceiling(difference.TotalDays)}d"; + + return string.Empty; + } + } + public class LeaderboardScoreStatistic { public IconUsage Icon; From 333a305af3dcabba5591b17140be49ba4c5c14f3 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 00:09:17 +0100 Subject: [PATCH 014/800] Use floor instead of ceiling --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index f77982535d..9ac1ab8075 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -408,11 +408,11 @@ namespace osu.Game.Online.Leaderboards if (difference.TotalMinutes < 1) return CommonStrings.TimeNow.ToString(); if (difference.TotalHours < 1) - return $@"{Math.Ceiling(difference.TotalMinutes)}min"; + return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) - return $@"{Math.Ceiling(difference.TotalHours)}h"; + return $@"{Math.Floor(difference.TotalHours)}h"; if (difference.TotalDays < 7) - return $@"{Math.Ceiling(difference.TotalDays)}d"; + return $@"{Math.Floor(difference.TotalDays)}d"; return string.Empty; } From 7bd731ae08210e58d2101aa3afef7807e15e4fa0 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 10:12:35 +0100 Subject: [PATCH 015/800] Move the date next to the flag icon --- .../Online/Leaderboards/LeaderboardScore.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9ac1ab8075..a13d8eeffd 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -44,7 +44,6 @@ namespace osu.Game.Online.Leaderboards private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 35; - private const float date_width = 35; protected Container RankContainer { get; private set; } @@ -59,7 +58,7 @@ namespace osu.Game.Online.Leaderboards public GlowingSpriteText ScoreText { get; private set; } - private Container flagBadgeContainer; + private FillFlowContainer flagBadgeAndDateContainer; private FillFlowContainer modsContainer; private List statisticsLabels; @@ -103,18 +102,10 @@ namespace osu.Game.Online.Leaderboards RelativeSizeAxes = Axes.Y, Width = rank_width, }, - new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = date_width, - Child = new DateLabel(Score.Date), - }, content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_width, Right = date_width, }, + Padding = new MarginPadding { Left = rank_width }, Children = new Drawable[] { new Container @@ -176,10 +167,12 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(10f, 0f), Children = new Drawable[] { - flagBadgeContainer = new Container + flagBadgeAndDateContainer = new FillFlowContainer { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5f, 0f), Size = new Vector2(87f, 20f), Masking = true, Children = new Drawable[] @@ -189,6 +182,10 @@ namespace osu.Game.Online.Leaderboards Width = 30, RelativeSizeAxes = Axes.Y, }, + new DateLabel(Score.Date) + { + RelativeSizeAxes = Axes.Y, + }, }, }, new FillFlowContainer @@ -254,7 +251,7 @@ namespace osu.Game.Online.Leaderboards public override void Show() { - foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeContainer, modsContainer }.Concat(statisticsLabels)) + foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels)) d.FadeOut(); Alpha = 0; @@ -281,7 +278,7 @@ namespace osu.Game.Online.Leaderboards using (BeginDelayedSequence(50)) { - var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray(); + var drawables = new Drawable[] { flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels).ToArray(); for (int i = 0; i < drawables.Length; i++) drawables[i].FadeIn(100 + i * 50); } @@ -391,10 +388,9 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { public DateLabel(DateTimeOffset date) - : base(date, 20) + : base(date) { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } protected override string Format() @@ -411,7 +407,7 @@ namespace osu.Game.Online.Leaderboards return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) return $@"{Math.Floor(difference.TotalHours)}h"; - if (difference.TotalDays < 7) + if (difference.TotalDays < 3) return $@"{Math.Floor(difference.TotalDays)}d"; return string.Empty; From f4d1e6f600343fda0ca9446297e9f8d0e5073ce6 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 10:38:29 +0100 Subject: [PATCH 016/800] Add tests for timerefs --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++++++ osu.Game/Online/Leaderboards/LeaderboardScore.cs | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 667fd08084..2f44633f4d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -197,6 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now, Mods = new Mod[] { new OsuModHidden(), @@ -234,6 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-30), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -254,6 +256,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-70), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -275,6 +278,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddMinutes(-40), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -296,6 +300,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-2), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -317,6 +322,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9826, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-25), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -338,6 +344,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9654, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-50), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -359,6 +366,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-72), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a13d8eeffd..8cfba68ea6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -401,8 +401,10 @@ namespace osu.Game.Online.Leaderboards // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) // requires pluralisable string support framework-side - if (difference.TotalMinutes < 1) + if (difference.TotalSeconds < 10) return CommonStrings.TimeNow.ToString(); + if (difference.TotalMinutes < 1) + return $@"{Math.Floor(difference.TotalSeconds)}s"; if (difference.TotalHours < 1) return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) From 1abbb9ab39675f4caeba0b9700aa9fb4fd9ca0be Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 21:26:59 +0100 Subject: [PATCH 017/800] Align the bar to be on baseline of score components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Online/Leaderboards/LeaderboardScore.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 8cfba68ea6..4f7670f098 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -160,8 +160,8 @@ namespace osu.Game.Online.Leaderboards }, new FillFlowContainer { - Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), @@ -169,29 +169,32 @@ namespace osu.Game.Online.Leaderboards { flagBadgeAndDateContainer = new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(5f, 0f), - Size = new Vector2(87f, 20f), + Width = 87f, Masking = true, Children = new Drawable[] { new UpdateableFlag(user.Country) { - Width = 30, - RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(30f, 20f), }, new DateLabel(Score.Date) { - RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, }, }, new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Margin = new MarginPadding { Left = edge_margin }, From 98aaf83177ee1cb8777619936e31926b2167b399 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 15:57:37 +0900 Subject: [PATCH 018/800] 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 019/800] 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 e49da2948d15d4d175d3de7ca7e47656e85e41b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:24:18 +0900 Subject: [PATCH 020/800] Fix storyboard background replacement logic not working for beatmaps with multiple backgrounds In the case where the background image of individual difficulties is different, querying the beatmap *set*'s metadata as we were will cause issues. I haven't added test coverage for this but can if required. Can be manually tested using https://osu.ppy.sh/beatmapsets/1595773#osu/3377474 (specifically the highest difficulty). Closes https://github.com/ppy/osu/discussions/16873. --- osu.Game/Storyboards/Storyboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index b86deeab89..c4864c0334 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -78,7 +78,7 @@ namespace osu.Game.Storyboards { get { - string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata.BackgroundFile; + string backgroundPath = BeatmapInfo.Metadata.BackgroundFile; if (string.IsNullOrEmpty(backgroundPath)) return false; From 420e2c538f01b1a1f387b7401a39d828698f83aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:48:30 +0900 Subject: [PATCH 021/800] 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 022/800] 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 023/800] 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 024/800] 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 025/800] 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 026/800] 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 027/800] 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 15ed9ec4fa725f08c6e49ca0192542f2b7aee6ac Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 20:47:02 +0100 Subject: [PATCH 028/800] Merge scoreboard and leaderboard implementations together --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 + .../Online/Leaderboards/LeaderboardScore.cs | 23 +------ .../BeatmapSet/Scores/ScoreboardTime.cs | 40 +---------- osu.Game/Utils/ScoreboardTimeUtils.cs | 66 +++++++++++++++++++ 4 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 osu.Game/Utils/ScoreboardTimeUtils.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2f44633f4d..c4178143f8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -388,6 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddMonths(-3), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -409,6 +410,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddYears(-2), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 4f7670f098..faec0abce6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -30,7 +30,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; -using osu.Game.Resources.Localisation.Web; +using osu.Framework.Utils; namespace osu.Game.Online.Leaderboards { @@ -398,24 +398,7 @@ namespace osu.Game.Online.Leaderboards protected override string Format() { - var now = DateTime.Now; - var difference = now - Date; - - // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - if (difference.TotalSeconds < 10) - return CommonStrings.TimeNow.ToString(); - if (difference.TotalMinutes < 1) - return $@"{Math.Floor(difference.TotalSeconds)}s"; - if (difference.TotalHours < 1) - return $@"{Math.Floor(difference.TotalMinutes)}min"; - if (difference.TotalDays < 1) - return $@"{Math.Floor(difference.TotalHours)}h"; - if (difference.TotalDays < 3) - return $@"{Math.Floor(difference.TotalDays)}d"; - - return string.Empty; + return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index ff1d3490b4..ceb446f310 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,9 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; using osu.Game.Graphics; -using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -16,41 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - { - var now = DateTime.Now; - var difference = now - Date; - - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - if (difference.TotalHours < 1) - return CommonStrings.TimeNow.ToString(); - if (difference.TotalDays < 1) - return "hr".ToQuantity((int)difference.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (Date > now.AddMonths(-1)) - return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; - - for (int months = 1; months <= 11; ++months) - { - if (Date > now.AddMonths(-(months + 1))) - return months == 1 ? "1mo" : $"{months}mos"; - } - - int years = 1; - while (Date <= now.AddYears(-(years + 1))) - years += 1; - return years == 1 ? "1yr" : $"{years}yrs"; - } + => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs new file mode 100644 index 0000000000..7e84b8f2d0 --- /dev/null +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -0,0 +1,66 @@ +// 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.Game.Resources.Localisation.Web; + +#nullable enable + +namespace osu.Game.Utils +{ + public static class ScoreboardTimeUtils + { + public static string FormatQuantity(string template, int quantity) + { + if (quantity <= 1) + return $@"{quantity}{template}"; + return $@"{quantity}{template}s"; + } + + public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) + { + // This function fails if the passed in time is something close to an epoch + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + var now = DateTime.Now; + var span = now - time; + + if (span < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (span.TotalMinutes < 1) + return FormatQuantity("sec", (int)span.TotalSeconds); + if (span.TotalHours < 1) + return FormatQuantity("min", (int)span.TotalMinutes); + if (span.TotalDays < 1) + return FormatQuantity("hr", (int)span.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return FormatQuantity("dy", (int)span.TotalDays); + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return FormatQuantity("mo", months); + } + + int years = 1; + // DateTime causes a crash here for epoch + while (time <= now.AddYears(-(years + 1))) + years += 1; + return FormatQuantity("yr", years); + } + } +} From 0d83c5a39a02581c3eb5d702c143ec55c904f461 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 20:47:30 +0100 Subject: [PATCH 029/800] Add colour highlighting recent scores --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index faec0abce6..217be5a024 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -390,6 +390,9 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { + public static readonly Colour4 COLOUR_SATURATED = Colour4.Lime; + public static readonly Colour4 COLOUR_UNSATURATED = Colour4.White; + public DateLabel(DateTimeOffset date) : base(date) { @@ -398,6 +401,15 @@ namespace osu.Game.Online.Leaderboards protected override string Format() { + var now = DateTime.Now; + var difference = now - Date; + + const double seconds_to_blank = 60*45; + const double tense_factor = 2.325; + + double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); + Colour = Interpolation.ValueAt(tf, COLOUR_SATURATED, COLOUR_UNSATURATED, 0, 1); + return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } } From 31b7ce053d24cef175f3b4b399d1a9e79b86fc32 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 21:18:26 +0100 Subject: [PATCH 030/800] Fix CI issues --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 217be5a024..aaf889d06c 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -404,7 +404,7 @@ namespace osu.Game.Online.Leaderboards var now = DateTime.Now; var difference = now - Date; - const double seconds_to_blank = 60*45; + const double seconds_to_blank = 60 * 45; const double tense_factor = 2.325; double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 7e84b8f2d0..86639c1034 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -14,13 +14,12 @@ namespace osu.Game.Utils { if (quantity <= 1) return $@"{quantity}{template}"; + return $@"{quantity}{template}s"; } public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) { - // This function fails if the passed in time is something close to an epoch - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. // this is intended to be a best-effort, more legible approximation of that. // compare: @@ -57,10 +56,13 @@ namespace osu.Game.Utils } int years = 1; - // DateTime causes a crash here for epoch - while (time <= now.AddYears(-(years + 1))) + // Add upper bound to prevent a crash + while (years < 20 && time <= now.AddYears(-(years + 1))) years += 1; - return FormatQuantity("yr", years); + if (years < 20) + return FormatQuantity("yr", years); + + return "never"; } } } From 262751a98a22d87ef5406203d5b38f94e65d30e0 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 21:23:35 +0100 Subject: [PATCH 031/800] Revert highlighting recent scores --- .../Online/Leaderboards/LeaderboardScore.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index aaf889d06c..a8b508829d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -390,28 +390,13 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { - public static readonly Colour4 COLOUR_SATURATED = Colour4.Lime; - public static readonly Colour4 COLOUR_UNSATURATED = Colour4.White; - public DateLabel(DateTimeOffset date) : base(date) { Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() - { - var now = DateTime.Now; - var difference = now - Date; - - const double seconds_to_blank = 60 * 45; - const double tense_factor = 2.325; - - double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); - Colour = Interpolation.ValueAt(tf, COLOUR_SATURATED, COLOUR_UNSATURATED, 0, 1); - - return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); - } + protected override string Format() => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic From e20ae5b87100a387045471902547cd2296841519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 17 Feb 2022 23:54:29 +0100 Subject: [PATCH 032/800] Add all colour constants for "basic" colour theme to `OsuColour` --- osu.Game/Graphics/OsuColour.cs | 57 +++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index f63bd53549..567d665e81 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -264,32 +264,53 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); - /// - /// Equivalent to 's . - /// - public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378"); + #region "Basic" colour theme - /// - /// Equivalent to 's . - /// + // Reference: https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Asset%2FColours?node-id=1838%3A3 + + public readonly Color4 Pink0 = Color4Extensions.FromHex(@"ff99c7"); + public readonly Color4 Pink1 = Color4Extensions.FromHex(@"ff66ab"); + public readonly Color4 Pink2 = Color4Extensions.FromHex(@"eb4791"); + public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378"); + public readonly Color4 Pink4 = Color4Extensions.FromHex(@"6b2e49"); + + public readonly Color4 Purple0 = Color4Extensions.FromHex(@"b299ff"); + public readonly Color4 Purple1 = Color4Extensions.FromHex(@"8c66ff"); + public readonly Color4 Purple2 = Color4Extensions.FromHex(@"7047eb"); + public readonly Color4 Purple3 = Color4Extensions.FromHex(@"5933cc"); + public readonly Color4 Purple4 = Color4Extensions.FromHex(@"3d2e6b"); + + public readonly Color4 Blue0 = Color4Extensions.FromHex(@"99ddff"); + public readonly Color4 Blue1 = Color4Extensions.FromHex(@"66ccff"); + public readonly Color4 Blue2 = Color4Extensions.FromHex(@"47b4eb"); public readonly Color4 Blue3 = Color4Extensions.FromHex(@"3399cc"); + public readonly Color4 Blue4 = Color4Extensions.FromHex(@"2e576b"); + + public readonly Color4 Green0 = Color4Extensions.FromHex(@"99ffa2"); + public readonly Color4 Green1 = Color4Extensions.FromHex(@"66ff73"); + public readonly Color4 Green2 = Color4Extensions.FromHex(@"47eb55"); + public readonly Color4 Green3 = Color4Extensions.FromHex(@"33cc40"); + public readonly Color4 Green4 = Color4Extensions.FromHex(@"2e6b33"); public readonly Color4 Lime0 = Color4Extensions.FromHex(@"ccff99"); - - /// - /// Equivalent to 's . - /// public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); - - /// - /// Equivalent to 's . - /// + public readonly Color4 Lime2 = Color4Extensions.FromHex(@"99eb47"); public readonly Color4 Lime3 = Color4Extensions.FromHex(@"7fcc33"); + public readonly Color4 Lime4 = Color4Extensions.FromHex(@"4c6b2e"); - /// - /// Equivalent to 's . - /// + public readonly Color4 Orange0 = Color4Extensions.FromHex(@"ffe699"); public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); + public readonly Color4 Orange2 = Color4Extensions.FromHex(@"ebc247"); + public readonly Color4 Orange3 = Color4Extensions.FromHex(@"cca633"); + public readonly Color4 Orange4 = Color4Extensions.FromHex(@"6b5c2e"); + + public readonly Color4 Red0 = Color4Extensions.FromHex(@"ff9b9b"); + public readonly Color4 Red1 = Color4Extensions.FromHex(@"ff6666"); + public readonly Color4 Red2 = Color4Extensions.FromHex(@"eb4747"); + public readonly Color4 Red3 = Color4Extensions.FromHex(@"cc3333"); + public readonly Color4 Red4 = Color4Extensions.FromHex(@"6b2e2e"); + + #endregion // Content Background public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28"); From 2592f0900d384e958cfbb6b99a638cb8089be134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:40:08 +0100 Subject: [PATCH 033/800] Add comments about `OverlayColourProvider` vs `OsuColour` distinction --- osu.Game/Graphics/OsuColour.cs | 5 +++++ osu.Game/Overlays/OverlayColourProvider.cs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 567d665e81..886ba7ef92 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -268,6 +268,11 @@ namespace osu.Game.Graphics // Reference: https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Asset%2FColours?node-id=1838%3A3 + // Note that the colours in this region are also defined in `OverlayColourProvider` as `Colour{0,1,2,3,4}`. + // The difference as to which should be used where comes down to context. + // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. + // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. + public readonly Color4 Pink0 = Color4Extensions.FromHex(@"ff99c7"); public readonly Color4 Pink1 = Color4Extensions.FromHex(@"ff66ab"); public readonly Color4 Pink2 = Color4Extensions.FromHex(@"eb4791"); diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index e7b3e6d873..730ca09411 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -25,6 +25,10 @@ namespace osu.Game.Overlays this.colourScheme = colourScheme; } + // Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`. + // The difference as to which should be used where comes down to context. + // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. + // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. public Color4 Colour1 => getColour(1, 0.7f); public Color4 Colour2 => getColour(0.8f, 0.6f); public Color4 Colour3 => getColour(0.6f, 0.5f); From 79ba37bbabc1fdd1acffcef16c4caac092066665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:40:39 +0100 Subject: [PATCH 034/800] Add `Colour0` to `OverlayColourProvider` --- osu.Game/Overlays/OverlayColourProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 730ca09411..c2ceef57e8 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -29,6 +29,7 @@ namespace osu.Game.Overlays // The difference as to which should be used where comes down to context. // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. + public Color4 Colour0 => getColour(1, 0.8f); public Color4 Colour1 => getColour(1, 0.7f); public Color4 Colour2 => getColour(0.8f, 0.6f); public Color4 Colour3 => getColour(0.6f, 0.5f); From ce0db9d4dbd7a0b030558002331dea4312be728a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:46:12 +0100 Subject: [PATCH 035/800] Remove all references to static `OverlayColourProvider`s --- .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 7 ++++++- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 7 +++++++ osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs | 2 +- osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index fb9e1c0420..51dad100c2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Game.Graphics; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; @@ -33,7 +35,10 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override Color4 GetStateColour() => OverlayColourProvider.Orange.Colour1; + [Resolved] + private OsuColour colours { get; set; } + + protected override Color4 GetStateColour() => colours.Orange1; } } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 9274cf20aa..52dfcad2cc 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -44,7 +44,14 @@ namespace osu.Game.Overlays.BeatmapListing }); Enabled.Value = true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + FinishTransforms(true); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs index ba78592ed2..21d1d1172c 100644 --- a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Orange.Colour2, + Colour = colours.Orange2 } } }; diff --git a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs index fdee0799ff..1be987cde2 100644 --- a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Blue.Colour1, + Colour = colours.Blue1 } } }; From 36a00c1ee238b13cfe857882110e1917cef5c49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:46:33 +0100 Subject: [PATCH 036/800] Remove static `OverlayColourProvider`s --- osu.Game/Overlays/OverlayColourProvider.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index c2ceef57e8..7bddb924a0 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -11,15 +11,6 @@ namespace osu.Game.Overlays { private readonly OverlayColourScheme colourScheme; - public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); - public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); - public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); - public static OverlayColourProvider Lime { get; } = new OverlayColourProvider(OverlayColourScheme.Lime); - public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); - public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); - public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); - public static OverlayColourProvider Plum { get; } = new OverlayColourProvider(OverlayColourScheme.Plum); - public OverlayColourProvider(OverlayColourScheme colourScheme) { this.colourScheme = colourScheme; From 2ded7d281b4b36e8d92ec12d72f1ba19ee33f935 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:17:19 +0900 Subject: [PATCH 037/800] Remove unused using statement --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a8b508829d..0e9968f27f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -30,7 +30,6 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; -using osu.Framework.Utils; namespace osu.Game.Online.Leaderboards { From 79408f6afcfd778e84c59ae329c73faf253dfa56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:30:58 +0900 Subject: [PATCH 038/800] Add xmldoc and clean up `ScoreboardTimeUtils` extension methods a touch --- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../BeatmapSet/Scores/ScoreboardTime.cs | 2 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 36 +++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 0e9968f27f..a9f03e5c9f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -395,7 +395,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index ceb446f310..1927e66edb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromHours(1)); + => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 86639c1034..4e079ff39f 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,15 +10,13 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - public static string FormatQuantity(string template, int quantity) - { - if (quantity <= 1) - return $@"{quantity}{template}"; - - return $@"{quantity}{template}s"; - } - - public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) { // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. // this is intended to be a best-effort, more legible approximation of that. @@ -36,23 +34,23 @@ namespace osu.Game.Utils return CommonStrings.TimeNow.ToString(); if (span.TotalMinutes < 1) - return FormatQuantity("sec", (int)span.TotalSeconds); + return formatQuantity("sec", (int)span.TotalSeconds); if (span.TotalHours < 1) - return FormatQuantity("min", (int)span.TotalMinutes); + return formatQuantity("min", (int)span.TotalMinutes); if (span.TotalDays < 1) - return FormatQuantity("hr", (int)span.TotalHours); + return formatQuantity("hr", (int)span.TotalHours); // this is where this gets more complicated because of how the calendar works. // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years // and test against cutoff dates to determine how many months/years to show. if (time > now.AddMonths(-1)) - return FormatQuantity("dy", (int)span.TotalDays); + return formatQuantity("dy", (int)span.TotalDays); for (int months = 1; months <= 11; ++months) { if (time > now.AddMonths(-(months + 1))) - return FormatQuantity("mo", months); + return formatQuantity("mo", months); } int years = 1; @@ -60,9 +58,17 @@ namespace osu.Game.Utils while (years < 20 && time <= now.AddYears(-(years + 1))) years += 1; if (years < 20) - return FormatQuantity("yr", years); + return formatQuantity("yr", years); return "never"; } + + private static string formatQuantity(string template, int quantity) + { + if (quantity <= 1) + return $@"{quantity}{template}"; + + return $@"{quantity}{template}s"; + } } } From c3b365cf6b79c8cfd618982fb163e6b7e72ed21e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 13:31:02 +0900 Subject: [PATCH 039/800] Scale classic score by hitobject count --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 15 ++++++++------- osu.Game/Scoring/ScoreManager.cs | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 79861c0ecc..42ab30d4d0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(mode, maxAchievableCombo, + return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); @@ -222,12 +222,11 @@ namespace osu.Game.Rulesets.Scoring /// Computes the total score. /// /// The to compute the total score in. - /// The maximum combo achievable in the beatmap. /// The accuracy percentage achieved by the player. - /// The proportion of achieved by the player. + /// The proportion of the max combo achieved by the player. /// Any statistics to be factored in. /// The total score. - public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, Dictionary statistics) + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) { switch (mode) { @@ -238,10 +237,12 @@ namespace osu.Game.Rulesets.Scoring return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: + int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. - double scaledStandardised = GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * (maxCombo + 1), 2) * 18; + double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; + return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; } } @@ -265,7 +266,7 @@ namespace osu.Game.Rulesets.Scoring computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; } - return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); + return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); } /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 532c6b42a3..963c4a77ca 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -184,7 +184,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.GetScore(mode, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + return (long)Math.Round(scoreProcessor.GetScore(mode, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } /// From fc1877b6faea3ad3bb9a476fc744d85dd6a463c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:42:26 +0900 Subject: [PATCH 040/800] Move to extension method and revert logic to match previous implementation --- osu.Game/Extensions/TimeDisplayExtensions.cs | 51 ++++++++++++++++++ .../Online/Leaderboards/LeaderboardScore.cs | 3 +- .../BeatmapSet/Scores/ScoreboardTime.cs | 4 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 52 ------------------- 4 files changed, 55 insertions(+), 55 deletions(-) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index dc05482a05..ee6f458398 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Extensions { @@ -47,5 +49,54 @@ namespace osu.Game.Extensions return new LocalisableFormattableString(timeSpan, @"mm\:ss"); } + + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) + { + var now = DateTime.Now; + var difference = now - time; + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (difference.TotalMinutes < 1) + return "sec".ToQuantity((int)difference.TotalSeconds); + if (difference.TotalHours < 1) + return "min".ToQuantity((int)difference.TotalMinutes); + if (difference.TotalDays < 1) + return "hr".ToQuantity((int)difference.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return months == 1 ? "1mo" : $"{months}mos"; + } + + int years = 1; + while (time <= now.AddYears(-(years + 1))) + years += 1; + return years == 1 ? "1yr" : $"{years}yrs"; + } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a9f03e5c9f..ddd9d9a2b2 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -395,7 +396,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index 1927e66edb..5018fb8c70 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Extensions; using osu.Game.Graphics; -using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); + => Date.ToShortRelativeTime(TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 4e079ff39f..ec222de018 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,58 +10,6 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - /// - /// Formats a provided date to a short relative string version for compact display. - /// - /// The time to be displayed. - /// A timespan denoting the time length beneath which "now" should be displayed. - /// A short relative string representing the input time. - public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) - { - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - var now = DateTime.Now; - var span = now - time; - - if (span < lowerCutoff) - return CommonStrings.TimeNow.ToString(); - - if (span.TotalMinutes < 1) - return formatQuantity("sec", (int)span.TotalSeconds); - if (span.TotalHours < 1) - return formatQuantity("min", (int)span.TotalMinutes); - if (span.TotalDays < 1) - return formatQuantity("hr", (int)span.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (time > now.AddMonths(-1)) - return formatQuantity("dy", (int)span.TotalDays); - - for (int months = 1; months <= 11; ++months) - { - if (time > now.AddMonths(-(months + 1))) - return formatQuantity("mo", months); - } - - int years = 1; - // Add upper bound to prevent a crash - while (years < 20 && time <= now.AddYears(-(years + 1))) - years += 1; - if (years < 20) - return formatQuantity("yr", years); - - return "never"; - } private static string formatQuantity(string template, int quantity) { From 3d0caa44c84f908a013eeac29bc126cfa20bacf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:43:30 +0900 Subject: [PATCH 041/800] Remove unused utils class --- osu.Game/Utils/ScoreboardTimeUtils.cs | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 osu.Game/Utils/ScoreboardTimeUtils.cs diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs deleted file mode 100644 index ec222de018..0000000000 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Game.Resources.Localisation.Web; - -#nullable enable - -namespace osu.Game.Utils -{ - public static class ScoreboardTimeUtils - { - - private static string formatQuantity(string template, int quantity) - { - if (quantity <= 1) - return $@"{quantity}{template}"; - - return $@"{quantity}{template}s"; - } - } -} From 656c58567dbe8068d7d4f7dfabcdf2b0818fbb87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 16:21:36 +0900 Subject: [PATCH 042/800] Add safeties to skip attempted import of the intro beatmap when osu! ruleset not present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In general running this import will not cause any critical failures, but the import itself *will* fail – and more loudly with the upcoming changes to `RulesetStore` (https://github.com/ppy/osu/pull/16890). Due to it being a loud failure, it will cause the notification overlay to display a parsing error, which can interrupt the flow of some tests. See test failure at https://github.com/ppy/osu/runs/5268848949?check_suite_focus=true as an example (video coverage at https://github.com/ppy/osu/pull/16890#issuecomment-1046542243). --- osu.Game/Screens/Menu/IntroScreen.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 98c4b15f7f..afe75c5ef7 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -19,6 +19,7 @@ using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -71,6 +72,9 @@ namespace osu.Game.Screens.Menu [CanBeNull] private readonly Func createNextScreen; + [Resolved] + private RulesetStore rulesets { get; set; } + /// /// Whether the is provided by osu! resources, rather than a user beatmap. /// Only valid during or after . @@ -117,7 +121,11 @@ namespace osu.Game.Screens.Menu // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. if (initialBeatmap == null) { - if (!loadThemedIntro()) + // Intro beatmaps are generally made using the osu! ruleset. + // It might not be present in test projects for other rulesets. + bool osuRulesetPresent = rulesets.GetRuleset(0) != null; + + if (!loadThemedIntro() && osuRulesetPresent) { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. From 2f6e65a9a268bf3c4b29a4005556cae95d524a31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 16:35:39 +0900 Subject: [PATCH 043/800] Gracefully handle undefined `DateTimeOffset` values Only seems to happen in tests, but best to safeguard against this regardless. --- osu.Game/Extensions/TimeDisplayExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index ee6f458398..54af6a5942 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -58,6 +58,9 @@ namespace osu.Game.Extensions /// A short relative string representing the input time. public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) { + if (time == default) + return "-"; + var now = DateTime.Now; var difference = now - time; From c466d6df94e741a5def1a4395a5d6d03b29240c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 17:19:35 +0900 Subject: [PATCH 044/800] Ensure to not multiply by 0 --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 42ab30d4d0..d5a5aa4592 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -239,6 +239,10 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. + if (totalHitObjects == 0) + totalHitObjects = 1; + // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; From 1737128334db2da3095f52e5fbcbb1c3fd169b8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:45:57 +0900 Subject: [PATCH 045/800] Allow room category to be copied even if `Spotlight` I remember that this conditional copy was added to support making copies of spotlight rooms without carrying across the `Spotlight` type, but in testing this is already handled web side to the point that it's not required. The rationale for allowing the copy is that this method is used for tests, where it was not being copied correctly from the input as expected (used at https://github.com/ppy/osu/blob/bdc3b76df0b58406796e2b08db13be7f2140fa7e/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs#L38). --- osu.Game/Online/Rooms/Room.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index bbe854f2dd..a328f8e8c0 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -168,8 +168,7 @@ namespace osu.Game.Online.Rooms RoomID.Value = other.RoomID.Value; Name.Value = other.Name.Value; - if (other.Category.Value != RoomCategory.Spotlight) - Category.Value = other.Category.Value; + Category.Value = other.Category.Value; if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) Host.Value = other.Host.Value; From ab8b502709f5da93709bcfc0f26a6e0053a9ccdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:59:51 +0900 Subject: [PATCH 046/800] Add test coverage of spotlights being at the top of the listing --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index c3d5f7ec23..9a66a2b05c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -44,15 +44,20 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddStep("add rooms", () => RoomManager.AddRooms(3)); + AddStep("add rooms", () => RoomManager.AddRooms(5)); - AddAssert("has 3 rooms", () => container.Rooms.Count == 3); - AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault())); - AddAssert("has 2 rooms", () => container.Rooms.Count == 2); + AddAssert("has 5 rooms", () => container.Rooms.Count == 5); + + AddAssert("all spotlights at top", () => container.Rooms + .SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight) + .All(r => r.Room.Category.Value == RoomCategory.Normal)); + + AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault(r => r.RoomID.Value == 0))); + AddAssert("has 4 rooms", () => container.Rooms.Count == 4); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); AddStep("select first room", () => container.Rooms.First().TriggerClick()); - AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First())); + AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); } [Test] From 02a85005004ae66dc4477bfd38932badc16ec894 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:59:09 +0900 Subject: [PATCH 047/800] Ensure spotlights always show at the top of the lounge listing As proposed at https://github.com/ppy/osu/discussions/16936. Spotlights are intended to have focus, so let's make sure they are the first thing the user sees for the time being. --- .../Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 9f917c978c..3260427192 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -124,7 +124,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateSorting() { foreach (var room in roomFlow) - roomFlow.SetLayoutPosition(room, -(room.Room.RoomID.Value ?? 0)); + { + roomFlow.SetLayoutPosition(room, room.Room.Category.Value == RoomCategory.Spotlight + // Always show spotlight playlists at the top of the listing. + ? float.MinValue + : -(room.Room.RoomID.Value ?? 0)); + } } protected override bool OnClick(ClickEvent e) From 46b408be75b21c3fe872227c298d696851150d3e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 17:59:50 +0900 Subject: [PATCH 048/800] Update tests to match new behaviour --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index f0d9ece06f..c6e7988543 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] [TestCase(ScoringMode.Standardised, HitResult.Ok, 800_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] - [TestCase(ScoringMode.Classic, HitResult.Meh, 41)] - [TestCase(ScoringMode.Classic, HitResult.Ok, 46)] - [TestCase(ScoringMode.Classic, HitResult.Great, 72)] + [TestCase(ScoringMode.Classic, HitResult.Meh, 20)] + [TestCase(ScoringMode.Classic, HitResult.Ok, 23)] + [TestCase(ScoringMode.Classic, HitResult.Great, 36)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { scoreProcessor.Mode.Value = scoringMode; @@ -86,17 +86,17 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 68)] - [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 81)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 109)] - [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 149)] - [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 149)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 9)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 15)] + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 86)] + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 104)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 140)] + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 190)] + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 190)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 18)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 31)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 149)] - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 18)] - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 18)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 12)] + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 36)] + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 36)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -128,8 +128,8 @@ namespace osu.Game.Tests.Rulesets.Scoring /// [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 69)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 60)] // (((3 * 0 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 34)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 30)] public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable From 7f4cc221d25c9d9733c5ec6189db61a3a0f0e2f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 18:33:27 +0900 Subject: [PATCH 049/800] 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 050/800] 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 051/800] 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 052/800] 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 053/800] 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 054/800] 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 655b23f408c483afb04916e16bfd4b15ebfbaf3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:46:31 +0900 Subject: [PATCH 055/800] Update playlist room display to a three column layout Similar to the changes made to multiplayer. --- .../Playlists/PlaylistsRoomSubScreen.cs | 227 ++++++++++-------- 1 file changed, 125 insertions(+), 102 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 542851cb0f..338a9c856f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -69,132 +69,155 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Room.MaxAttempts.BindValueChanged(attempts => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); } - protected override Drawable CreateMainContent() => new GridContainer + protected override Drawable CreateMainContent() => new Container { RelativeSizeAxes = Axes.Both, - Content = new[] + Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, + Child = new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - new Container + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - Child = new GridContainer + // Playlist items column + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedPlaylistHeader(), }, + new Drawable[] + { + new DrawableRoomPlaylist + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = Room.Playlist }, + SelectedItem = { BindTarget = SelectedItem }, + AllowSelection = true, + AllowShowingResults = true, + RequestResults = item => + { + Debug.Assert(RoomId.Value != null); + ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + } + }, + // Spacer + null, + // Middle column (mods and leaderboard) + new GridContainer { RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new OverlinedPlaylistHeader(), }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new UserModSelectButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } + } + }, + }, new Drawable[] { - new DrawableRoomPlaylist + progressSection = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, - SelectedItem = { BindTarget = SelectedItem }, - AllowSelection = true, - AllowShowingResults = true, - RequestResults = item => + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + new OverlinedHeader("Progress"), + new RoomLocalUserInfo(), } - } + }, }, + new Drawable[] + { + new OverlinedHeader("Leaderboard") + }, + new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Chat") }, + new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(), } - } - }, - null, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new[] - { - UserModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Children = new Drawable[] - { - new OverlinedHeader("Extra mods"), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new UserModSelectButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 90, - Text = "Select", - Action = ShowUserModSelect, - }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = UserMods, - Scale = new Vector2(0.8f), - }, - } - } - } - }, - }, - new Drawable[] - { - progressSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new OverlinedHeader("Progress"), - new RoomLocalUserInfo(), - } - }, - }, - new Drawable[] - { - new OverlinedHeader("Leaderboard") - }, - new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, - new Drawable[] { new OverlinedHeader("Chat"), }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120), - } }, }, - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), - new Dimension(), - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), } }; From 98c008b95f49ac3c1676619aa9195cce4ad46c54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:48:39 +0900 Subject: [PATCH 056/800] Fix test failures due to order change --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 2 +- osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 9a66a2b05c..93cd281bc5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddStep("add rooms", () => RoomManager.AddRooms(5)); + AddStep("add rooms", () => RoomManager.AddRooms(5, withSpotlightRooms: true)); AddAssert("has 5 rooms", () => container.Rooms.Count == 5); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 8dfd969c51..6abcb2924c 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay base.JoinRoom(room, password, onSuccess, onError); } - public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false) + public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) { for (int i = 0; i < count; i++) { @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay Name = { Value = $@"Room {currentRoomId}" }, Host = { Value = new APIUser { Username = @"Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, + Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, }; if (withPassword) From 2aa0364092819f821cd1be69c4d56e2150a1218d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 00:14:33 +0900 Subject: [PATCH 057/800] 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 058/800] 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 059/800] 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 060/800] 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 061/800] 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 062/800] 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 063/800] 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 064/800] 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 065/800] 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 066/800] 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 d6032a2335e0de00dae43ce7f6fcef95ffbbe50e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:55:52 +0900 Subject: [PATCH 067/800] Fix beatmap overlay not re-fetching results after login --- .../BeatmapListing/BeatmapListingFilterControl.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 157753c09f..0f87f04270 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -67,6 +67,8 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private IAPIProvider api { get; set; } + private IBindable apiUser; + public BeatmapListingFilterControl() { RelativeSizeAxes = Axes.X; @@ -127,7 +129,7 @@ namespace osu.Game.Overlays.BeatmapListing } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, IAPIProvider api) { sortControlBackground.Colour = colourProvider.Background4; } @@ -161,6 +163,9 @@ namespace osu.Game.Overlays.BeatmapListing sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); + + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(_ => queueUpdateSearch()); } public void TakeFocus() => searchControl.TakeFocus(); @@ -190,6 +195,9 @@ namespace osu.Game.Overlays.BeatmapListing resetSearch(); + if (!api.IsLoggedIn) + return; + queryChangedDebounce = Scheduler.AddDelayed(() => { resetSearch(); From e744840d07aec8f0f6a75abb81ba1bc9dd9dbba8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:56:04 +0900 Subject: [PATCH 068/800] Ensure old results are cleared from beatmap overlay on logout --- osu.Game/Overlays/BeatmapListingOverlay.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index fbed234cc7..3476968ded 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Localisation; using osu.Framework.Graphics; @@ -19,6 +20,7 @@ using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing; using osu.Game.Resources.Localisation.Web; @@ -32,6 +34,11 @@ namespace osu.Game.Overlays [Resolved] private PreviewTrackManager previewTrackManager { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + + private IBindable apiUser; + private Drawable currentContent; private Container panelTarget; private FillFlowContainer foundContent; @@ -93,6 +100,13 @@ namespace osu.Game.Overlays { base.LoadComplete(); filterControl.CardSize.BindValueChanged(_ => onCardSizeChanged()); + + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(_ => + { + if (api.IsLoggedIn) + addContentToResultsArea(Drawable.Empty()); + }); } public void ShowWithSearch(string query) From cde3d9c08ba8e3b9af8e8e6192c9febdfa306baa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:15:57 +0900 Subject: [PATCH 069/800] 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 070/800] 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 071/800] 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 347a2346b9d73352a482b882d81ad49cf8ed4176 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:40:30 +0900 Subject: [PATCH 072/800] Fix `TestSceneEditorSaving` not waiting for timeline load As seen at https://github.com/ppy/osu/runs/5276431764?check_suite_focus=true. --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index adaa24d542..d1c1558003 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -49,6 +49,8 @@ namespace osu.Game.Tests.Visual.Editing double originalTimelineZoom = 0; double changedTimelineZoom = 0; + AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + AddStep("Set beat divisor", () => Editor.Dependencies.Get().Value = 16); AddStep("Set timeline zoom", () => { From 61b3280de157b86d6b6ad61558902c491e617fc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:47:00 +0900 Subject: [PATCH 073/800] 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 074/800] 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 075/800] 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 076/800] 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 077/800] 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 078/800] 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 079/800] 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 7bdcb5952ed67bc895da12833767bc3746326d85 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 23 Feb 2022 00:36:56 +0100 Subject: [PATCH 080/800] Fix handling badly-formatted osu:// urls --- osu.Game/Online/Chat/MessageFormatter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d7974004b1..3517777325 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -236,8 +236,7 @@ namespace osu.Game.Online.Chat break; default: - linkType = LinkAction.External; - break; + return new LinkDetails(LinkAction.External, url); } return new LinkDetails(linkType, args[2]); From 2bea485af8da9bddd5fe404dd71e967a3c9b631b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Feb 2022 13:37:45 +0900 Subject: [PATCH 081/800] 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 082/800] 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 083/800] 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 084/800] 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 085/800] 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 086/800] 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 087/800] 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 088/800] 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 089/800] 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 090/800] 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 091/800] 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 5ff4d3f8e5fca93f662bf4d0d0d171b712a7c43f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 19:05:46 +0900 Subject: [PATCH 092/800] Add support to `SpectatorClient` to resend failed frame bundles --- osu.Game/Online/Spectator/SpectatorClient.cs | 48 ++++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index a54ea0d9ee..ba0a9a77a0 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -168,8 +168,6 @@ namespace osu.Game.Online.Spectator }); } - public void SendFrames(FrameDataBundle data) => lastSend = SendFramesInternal(data); - public void EndPlaying(GameplayState state) { // This method is most commonly called via Dispose(), which is can be asynchronous (via the AsyncDisposalQueue). @@ -180,7 +178,7 @@ namespace osu.Game.Online.Spectator return; if (pendingFrames.Count > 0) - purgePendingFrames(true); + purgePendingFrames(); IsPlaying = false; currentBeatmap = null; @@ -230,9 +228,11 @@ namespace osu.Game.Online.Spectator protected abstract Task StopWatchingUserInternal(int userId); + private readonly Queue pendingFrameBundles = new Queue(); + private readonly Queue pendingFrames = new Queue(); - private double lastSendTime; + private double lastPurgeTime; private Task? lastSend; @@ -242,7 +242,7 @@ namespace osu.Game.Online.Spectator { base.Update(); - if (pendingFrames.Count > 0 && Time.Current - lastSendTime > TIME_BETWEEN_SENDS) + if (pendingFrames.Count > 0 && Time.Current - lastPurgeTime > TIME_BETWEEN_SENDS) purgePendingFrames(); } @@ -260,23 +260,41 @@ namespace osu.Game.Online.Spectator purgePendingFrames(); } - private void purgePendingFrames(bool force = false) + private void purgePendingFrames() { - if (lastSend?.IsCompleted == false && !force) - return; - if (pendingFrames.Count == 0) return; - var frames = pendingFrames.ToArray(); - - pendingFrames.Clear(); - Debug.Assert(currentScore != null); - SendFrames(new FrameDataBundle(currentScore.ScoreInfo, frames)); + var frames = pendingFrames.ToArray(); + var bundle = new FrameDataBundle(currentScore.ScoreInfo, frames); - lastSendTime = Time.Current; + pendingFrames.Clear(); + lastPurgeTime = Time.Current; + + pendingFrameBundles.Enqueue(bundle); + + sendNextBundleIfRequired(); + } + + private void sendNextBundleIfRequired() + { + if (lastSend?.IsCompleted == false) + return; + + if (!pendingFrameBundles.TryPeek(out var bundle)) + return; + + lastSend = SendFramesInternal(bundle); + lastSend.ContinueWith(t => Schedule(() => + { + // If the last bundle send wasn't successful, try again without dequeuing. + if (t.IsCompletedSuccessfully) + pendingFrameBundles.Dequeue(); + + sendNextBundleIfRequired(); + })); } } } From cff6f854724c8ce4f8ba8935734664a5e5f7c048 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 19:08:32 +0900 Subject: [PATCH 093/800] Add note about reconnection being insufficient currently --- osu.Game/Online/Spectator/SpectatorClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index ba0a9a77a0..7afceb41a4 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -94,6 +94,7 @@ namespace osu.Game.Online.Spectator // re-send state in case it wasn't received if (IsPlaying) + // TODO: this is likely sent out of order after a reconnect scenario. needs further consideration. BeginPlayingInternal(currentState); } else From 14c8ce50a0849fb97882db3f9dd2a9553ac118bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 17:56:07 +0900 Subject: [PATCH 094/800] Prefix all test send methods in `TestSpectatorClient` with `Send` --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 10 +++++----- .../TestSceneMultiSpectatorLeaderboard.cs | 2 +- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 12 ++++++------ .../TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../Online/TestSceneCurrentlyPlayingDisplay.cs | 4 ++-- .../Tests/Visual/Spectator/TestSpectatorClient.cs | 7 ++----- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d47ebf9f0d..d6f8c7addf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -257,7 +257,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Passed)); + AddStep("send passed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Passed)); AddUntilStep("state is passed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Passed); start(); @@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id)); + AddStep("send quit", () => spectatorClient.SendEndPlay(streamingUser.Id)); AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit); start(); @@ -293,7 +293,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Failed)); + AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed)); AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); start(); @@ -312,9 +312,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true); - private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); + private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.SendStartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish(SpectatedUserState state = SpectatedUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state)); + private void finish(SpectatedUserState state = SpectatedUserState.Quit) => AddStep("end play", () => spectatorClient.SendEndPlay(streamingUser.Id, state)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 6b3573b3cb..488ecdb8af 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach ((int userId, var _) in clocks) { - SpectatorClient.StartPlay(userId, 0); + SpectatorClient.SendStartPlay(userId, 0); OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = userId }); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 7ce0c6a94d..a785301f62 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -70,11 +70,11 @@ namespace osu.Game.Tests.Visual.Multiplayer loadSpectateScreen(false); AddWaitStep("wait a bit", 10); - AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); + AddStep("load player first_player_id", () => SpectatorClient.SendStartPlay(PLAYER_1_ID, importedBeatmapId)); AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); - AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); + AddStep("load player second_player_id", () => SpectatorClient.SendStartPlay(PLAYER_2_ID, importedBeatmapId)); AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } @@ -133,8 +133,8 @@ namespace osu.Game.Tests.Visual.Multiplayer TeamID = 1, }; - SpectatorClient.StartPlay(player1.UserID, importedBeatmapId); - SpectatorClient.StartPlay(player2.UserID, importedBeatmapId); + SpectatorClient.SendStartPlay(player1.UserID, importedBeatmapId); + SpectatorClient.SendStartPlay(player2.UserID, importedBeatmapId); playingUsers.Add(player1); playingUsers.Add(player2); @@ -397,7 +397,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; OnlinePlayDependencies.MultiplayerClient.AddUser(user.User, true); - SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); + SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId); playingUsers.Add(user); } @@ -411,7 +411,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var user = playingUsers.Single(u => u.UserID == userId); OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull()); - SpectatorClient.EndPlay(userId); + SpectatorClient.SendEndPlay(userId); playingUsers.Remove(user); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 6605f82d5c..cfac5da4ff 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); + SpectatorClient.SendStartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); multiplayerUsers.Add(OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = user }, true)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index dabc1c1e5a..f751b162d1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); + SpectatorClient.SendStartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); var roomUser = OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = user }, true); roomUser.MatchState = new TeamVersusUserState diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index b5a03b558d..35a4f8cf2d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -56,9 +56,9 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestBasicDisplay() { - AddStep("Add playing user", () => spectatorClient.StartPlay(streamingUser.Id, 0)); + AddStep("Add playing user", () => spectatorClient.SendStartPlay(streamingUser.Id, 0)); AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType()?.FirstOrDefault()?.User.Id == 2); - AddStep("Remove playing user", () => spectatorClient.EndPlay(streamingUser.Id)); + AddStep("Remove playing user", () => spectatorClient.SendEndPlay(streamingUser.Id)); AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType().Any()); } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 1322a99ea7..741b489d95 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -3,7 +3,6 @@ #nullable enable -using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; @@ -47,7 +46,7 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to start play for. /// The playing beatmap id. - public void StartPlay(int userId, int beatmapId) + public void SendStartPlay(int userId, int beatmapId) { userBeatmapDictionary[userId] = beatmapId; userNextFrameDictionary[userId] = 0; @@ -59,7 +58,7 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to end play for. /// The spectator state to end play with. - public void EndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) + public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) { if (!userBeatmapDictionary.ContainsKey(userId)) return; @@ -74,8 +73,6 @@ namespace osu.Game.Tests.Visual.Spectator userBeatmapDictionary.Remove(userId); } - public new void Schedule(Action action) => base.Schedule(action); - /// /// Sends frames for an arbitrary user, in bundles containing 10 frames each. /// From c94e7e2abe98799d9372b2d0ba4f5fc91fc668a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:18:35 +0900 Subject: [PATCH 095/800] Add ability to simulate network failures to `TestSpectatorClient` --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../TestSceneMultiSpectatorLeaderboard.cs | 4 ++-- .../TestSceneMultiSpectatorScreen.cs | 4 ++-- .../Visual/Spectator/TestSpectatorClient.cs | 20 ++++++++++++++++--- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d6f8c7addf..d614815316 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -321,7 +321,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void sendFrames(int count = 10) { - AddStep("send frames", () => spectatorClient.SendFrames(streamingUser.Id, count)); + AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count)); } private void loadSpectatingScreen() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 488ecdb8af..42bb99de24 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -68,10 +68,10 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - SpectatorClient.SendFrames(PLAYER_1_ID, 1); + SpectatorClient.SendFramesFromUser(PLAYER_1_ID, 1); if (i % 10 == 0) - SpectatorClient.SendFrames(PLAYER_2_ID, 10); + SpectatorClient.SendFramesFromUser(PLAYER_2_ID, 10); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a785301f62..4b89efe858 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -360,7 +360,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay(). // (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete) - AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100)); + AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFramesFromUser(PLAYER_1_ID, 100)); AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded); @@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("send frames", () => { foreach (int id in userIds) - SpectatorClient.SendFrames(id, count); + SpectatorClient.SendFramesFromUser(id, count); }); } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 741b489d95..ae5d20f4f4 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; @@ -20,10 +21,15 @@ namespace osu.Game.Tests.Visual.Spectator public class TestSpectatorClient : SpectatorClient { /// - /// Maximum number of frames sent per bundle via . + /// Maximum number of frames sent per bundle via . /// public const int FRAME_BUNDLE_SIZE = 10; + /// + /// Whether to force send operations to fail (simulating a network issue). + /// + public bool ShouldFailSendingFrames { get; set; } + public override IBindable IsConnected { get; } = new Bindable(true); public IReadOnlyDictionary LastReceivedUserFrames => lastReceivedUserFrames; @@ -75,10 +81,12 @@ namespace osu.Game.Tests.Visual.Spectator /// /// Sends frames for an arbitrary user, in bundles containing 10 frames each. + /// This bypasses the standard queueing mechanism completely and should only be used to test cases where multiple users need to be sending data. + /// Importantly, will have no effect. /// /// The user to send frames for. /// The total number of frames to send. - public void SendFrames(int userId, int count) + public void SendFramesFromUser(int userId, int count) { var frames = new List(); @@ -120,7 +128,13 @@ namespace osu.Game.Tests.Visual.Spectator return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state); } - protected override Task SendFramesInternal(FrameDataBundle data) => ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, data); + protected override Task SendFramesInternal(FrameDataBundle bundle) + { + if (ShouldFailSendingFrames) + return Task.FromException(new InvalidOperationException()); + + return ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, bundle); + } protected override Task EndPlayingInternal(SpectatorState state) => ((ISpectatorClient)this).UserFinishedPlaying(api.LocalUser.Value.Id, state); From 47b84295a6c32c4b9ac4ae4a8cd6d3d26d5206be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:19:12 +0900 Subject: [PATCH 096/800] Fix bundle send logic not correctly waiting for task completion (due to nested schedule) --- osu.Game/Online/Spectator/SpectatorClient.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 7afceb41a4..428a4cdbdb 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -221,8 +221,7 @@ namespace osu.Game.Online.Spectator protected abstract Task BeginPlayingInternal(SpectatorState state); - protected abstract Task SendFramesInternal(FrameDataBundle data); - + protected abstract Task SendFramesInternal(FrameDataBundle bundle); protected abstract Task EndPlayingInternal(SpectatorState state); protected abstract Task WatchUserInternal(int userId); @@ -281,19 +280,27 @@ namespace osu.Game.Online.Spectator private void sendNextBundleIfRequired() { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (lastSend?.IsCompleted == false) return; if (!pendingFrameBundles.TryPeek(out var bundle)) return; - lastSend = SendFramesInternal(bundle); - lastSend.ContinueWith(t => Schedule(() => + TaskCompletionSource tcs = new TaskCompletionSource(); + + lastSend = tcs.Task; + + SendFramesInternal(bundle).ContinueWith(t => Schedule(() => { + bool wasSuccessful = t.Exception == null; + // If the last bundle send wasn't successful, try again without dequeuing. - if (t.IsCompletedSuccessfully) + if (wasSuccessful) pendingFrameBundles.Dequeue(); + tcs.SetResult(wasSuccessful); sendNextBundleIfRequired(); })); } From 260cf793fefe2e3f862373e2f35c7c1bf18de8ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:18:42 +0900 Subject: [PATCH 097/800] Add test coverage of more advanced frame delivery scenarios to `TestSceneSpectatorPlayback` --- .../Gameplay/TestSceneSpectatorPlayback.cs | 57 +++++++++++++++---- .../Visual/Spectator/TestSpectatorClient.cs | 4 ++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index a4d8460846..4ec46036f6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -41,8 +41,12 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; + private TestSpectatorClient spectatorClient; + private ManualClock manualClock; + private TestReplayRecorder recorder; + private OsuSpriteText latencyDisplay; private TestFramedReplayInputHandler replayHandler; @@ -54,7 +58,6 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); manualClock = new ManualClock(); - SpectatorClient spectatorClient; Child = new DependencyProvidingContainer { @@ -76,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder + Recorder = recorder = new TestReplayRecorder { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, @@ -143,22 +146,52 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestBasic() + { + AddUntilStep("received frames", () => replay.Frames.Count > 50); + AddStep("stop sending frames", () => recorder.Expire()); + AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count); + } + + [Test] + public void TestWithSendFailure() + { + AddUntilStep("received frames", () => replay.Frames.Count > 50); + + int framesReceivedSoFar = 0; + int frameSendAttemptsSoFar = 0; + + AddStep("start failing sends", () => + { + spectatorClient.ShouldFailSendingFrames = true; + framesReceivedSoFar = replay.Frames.Count; + frameSendAttemptsSoFar = spectatorClient.FrameSendAttempts; + }); + + AddUntilStep("wait for send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 5); + AddAssert("frames did not increase", () => framesReceivedSoFar == replay.Frames.Count); + + AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false); + + AddUntilStep("wait for next frames", () => framesReceivedSoFar < replay.Frames.Count); + + AddStep("stop sending frames", () => recorder.Expire()); + + AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count); + AddAssert("ensure frames were received in the correct sequence", () => replay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time))); + } + private void onNewFrames(int userId, FrameDataBundle frames) { - Logger.Log($"Received {frames.Frames.Count} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); - foreach (var legacyFrame in frames.Frames) { var frame = new TestReplayFrame(); frame.FromLegacy(legacyFrame, null); replay.Frames.Add(frame); } - } - [Test] - public void TestBasic() - { - AddStep("Wait for user input", () => { }); + Logger.Log($"Received {frames.Frames.Count} new frames (total {replay.Frames.Count} of {recorder.SentFrames.Count})"); } private double latency = SpectatorClient.TIME_BETWEEN_SENDS; @@ -318,6 +351,8 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { + public List SentFrames = new List(); + public TestReplayRecorder() : base(new Score { @@ -332,7 +367,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) { - return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + var testReplayFrame = new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + SentFrames.Add(testReplayFrame); + return testReplayFrame; } } } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index ae5d20f4f4..f5da95bd7b 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -30,6 +30,8 @@ namespace osu.Game.Tests.Visual.Spectator /// public bool ShouldFailSendingFrames { get; set; } + public int FrameSendAttempts { get; private set; } + public override IBindable IsConnected { get; } = new Bindable(true); public IReadOnlyDictionary LastReceivedUserFrames => lastReceivedUserFrames; @@ -130,6 +132,8 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task SendFramesInternal(FrameDataBundle bundle) { + FrameSendAttempts++; + if (ShouldFailSendingFrames) return Task.FromException(new InvalidOperationException()); From 694c6ad872c87aeabdc1d430fbadf082f24463bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 00:59:25 +0900 Subject: [PATCH 098/800] Fix frames potentially getting lost due to non-matching `Schedule` usage --- osu.Game/Online/Spectator/SpectatorClient.cs | 38 ++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 428a4cdbdb..0620643bc3 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Replays.Legacy; @@ -156,6 +157,8 @@ namespace osu.Game.Online.Spectator IsPlaying = true; + totalBundledFrames = 0; + // transfer state at point of beginning play currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; @@ -169,6 +172,21 @@ namespace osu.Game.Online.Spectator }); } + public void HandleFrame(ReplayFrame frame) => Schedule(() => + { + if (!IsPlaying) + { + Logger.Log($"Frames arrived at {nameof(SpectatorClient)} outside of gameplay scope and will be ignored."); + return; + } + + if (frame is IConvertibleReplayFrame convertible) + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); + + if (pendingFrames.Count > max_pending_frames) + purgePendingFrames(); + }); + public void EndPlaying(GameplayState state) { // This method is most commonly called via Dispose(), which is can be asynchronous (via the AsyncDisposalQueue). @@ -236,6 +254,8 @@ namespace osu.Game.Online.Spectator private Task? lastSend; + private int totalBundledFrames; + private const int max_pending_frames = 30; protected override void Update() @@ -246,20 +266,6 @@ namespace osu.Game.Online.Spectator purgePendingFrames(); } - public void HandleFrame(ReplayFrame frame) - { - Debug.Assert(ThreadSafety.IsUpdateThread); - - if (!IsPlaying) - return; - - if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); - - if (pendingFrames.Count > max_pending_frames) - purgePendingFrames(); - } - private void purgePendingFrames() { if (pendingFrames.Count == 0) @@ -270,6 +276,10 @@ namespace osu.Game.Online.Spectator var frames = pendingFrames.ToArray(); var bundle = new FrameDataBundle(currentScore.ScoreInfo, frames); + totalBundledFrames += frames.Length; + + Console.WriteLine($"Purging {pendingFrames.Count} frames (total {totalBundledFrames})"); + pendingFrames.Clear(); lastPurgeTime = Time.Current; From 5ffdd57895bd61652855683e2064713006ae7fda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:19:37 +0900 Subject: [PATCH 099/800] Rename weirdly named parameter --- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index ddde69c627..4d6ca0b311 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -57,14 +57,14 @@ namespace osu.Game.Online.Spectator return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); } - protected override Task SendFramesInternal(FrameDataBundle data) + protected override Task SendFramesInternal(FrameDataBundle bundle) { if (!IsConnected.Value) return Task.CompletedTask; Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), bundle); } protected override Task EndPlayingInternal(SpectatorState state) From 3f2ef030e4a1d82978643b8e31a080629c8654a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:29:49 +0900 Subject: [PATCH 100/800] Group `SpectatorClient` private fields together --- osu.Game/Online/Spectator/SpectatorClient.cs | 49 ++++++++++---------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 0620643bc3..d0b89e981d 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -46,18 +46,6 @@ namespace osu.Game.Online.Spectator /// public IBindableList PlayingUsers => playingUsers; - /// - /// All users currently being watched. - /// - private readonly List watchedUsers = new List(); - - private readonly BindableDictionary watchedUserStates = new BindableDictionary(); - private readonly BindableList playingUsers = new BindableList(); - private readonly SpectatorState currentState = new SpectatorState(); - - private IBeatmap? currentBeatmap; - private Score? currentScore; - /// /// Whether the local user is playing. /// @@ -78,6 +66,30 @@ namespace osu.Game.Online.Spectator /// public event Action? OnUserFinishedPlaying; + /// + /// All users currently being watched. + /// + private readonly List watchedUsers = new List(); + + private readonly BindableDictionary watchedUserStates = new BindableDictionary(); + private readonly BindableList playingUsers = new BindableList(); + private readonly SpectatorState currentState = new SpectatorState(); + + private IBeatmap? currentBeatmap; + private Score? currentScore; + + private readonly Queue pendingFrameBundles = new Queue(); + + private readonly Queue pendingFrames = new Queue(); + + private double lastPurgeTime; + + private Task? lastSend; + + private int totalBundledFrames; + + private const int max_pending_frames = 30; + [BackgroundDependencyLoader] private void load() { @@ -240,24 +252,13 @@ namespace osu.Game.Online.Spectator protected abstract Task BeginPlayingInternal(SpectatorState state); protected abstract Task SendFramesInternal(FrameDataBundle bundle); + protected abstract Task EndPlayingInternal(SpectatorState state); protected abstract Task WatchUserInternal(int userId); protected abstract Task StopWatchingUserInternal(int userId); - private readonly Queue pendingFrameBundles = new Queue(); - - private readonly Queue pendingFrames = new Queue(); - - private double lastPurgeTime; - - private Task? lastSend; - - private int totalBundledFrames; - - private const int max_pending_frames = 30; - protected override void Update() { base.Update(); 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 101/800] 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 102/800] 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 103/800] 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 104/800] 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 105/800] 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 106/800] 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 107/800] 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 108/800] 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 109/800] 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 110/800] 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 111/800] 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 112/800] 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 113/800] 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 114/800] 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 115/800] 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 116/800] 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 117/800] 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 118/800] 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 119/800] 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 120/800] 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 121/800] 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. From 8eef1774d5b2b6a58f67a17d1ca3804ce2dbbf76 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 25 Feb 2022 20:18:22 +0900 Subject: [PATCH 122/800] Use Logger.Log instead of console --- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index d0b89e981d..70baf69037 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -279,7 +279,7 @@ namespace osu.Game.Online.Spectator totalBundledFrames += frames.Length; - Console.WriteLine($"Purging {pendingFrames.Count} frames (total {totalBundledFrames})"); + Logger.Log($"Purging {pendingFrames.Count} frames (total {totalBundledFrames})"); pendingFrames.Clear(); lastPurgeTime = Time.Current; From 67082b8c5de9d97d353ea973f34bc6773af4029f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Feb 2022 22:25:36 +0900 Subject: [PATCH 123/800] Remove verbose logging from `SpectatorClient` for now --- osu.Game/Online/Spectator/SpectatorClient.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 70baf69037..8f22078010 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -86,8 +86,6 @@ namespace osu.Game.Online.Spectator private Task? lastSend; - private int totalBundledFrames; - private const int max_pending_frames = 30; [BackgroundDependencyLoader] @@ -169,8 +167,6 @@ namespace osu.Game.Online.Spectator IsPlaying = true; - totalBundledFrames = 0; - // transfer state at point of beginning play currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; @@ -277,10 +273,6 @@ namespace osu.Game.Online.Spectator var frames = pendingFrames.ToArray(); var bundle = new FrameDataBundle(currentScore.ScoreInfo, frames); - totalBundledFrames += frames.Length; - - Logger.Log($"Purging {pendingFrames.Count} frames (total {totalBundledFrames})"); - pendingFrames.Clear(); lastPurgeTime = Time.Current; From 926108af7da97c9f0114702ba94adc0f3643c4c7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 25 Feb 2022 14:15:19 +0100 Subject: [PATCH 124/800] Change launch args from dotnet 5 to dotnet 6 --- .vscode/launch.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1b590008cd..d93fddf42d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll" + "${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", @@ -19,7 +19,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll" + "${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", @@ -31,7 +31,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Debug/net5.0/osu.Game.Tests.dll" + "${workspaceRoot}/osu.Game.Tests/bin/Debug/net6.0/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Debug)", @@ -43,7 +43,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Release/net5.0/osu.Game.Tests.dll" + "${workspaceRoot}/osu.Game.Tests/bin/Release/net6.0/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Release)", @@ -55,7 +55,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll", + "${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -68,7 +68,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll", + "${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -81,7 +81,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll", + "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -94,7 +94,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll", + "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -105,7 +105,7 @@ "name": "Benchmark", "type": "coreclr", "request": "launch", - "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll", + "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net6.0/osu.Game.Benchmarks.dll", "args": [ "--filter", "*" From 1a7a160f0a5a10345fd1537bf106022589038b18 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 25 Feb 2022 14:26:44 +0100 Subject: [PATCH 125/800] Update vscode launch.json files for all other projects --- .../osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json | 4 ++-- .../.vscode/launch.json | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Catch.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Mania.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Osu.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json | 4 ++-- osu.Game.Tournament.Tests/.vscode/launch.json | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json index fd03878699..b433819346 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json index bd9db14259..d60bc2571d 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json index 24e4873ed6..f1f37f6363 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json index bd9db14259..d60bc2571d 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json index 9aaaf418c2..201343a036 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json index e3d7956e85..f6a067a831 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json index 01a5985464..61be25b845 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json index 779ebba9ae..56ec7d8d9c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Tournament.Tests/.vscode/launch.json b/osu.Game.Tournament.Tests/.vscode/launch.json index 28532d3ed3..51aa541811 100644 --- a/osu.Game.Tournament.Tests/.vscode/launch.json +++ b/osu.Game.Tournament.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Tournament.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Tournament.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", From c7f2759eb92385acb9e80f1cfd5addc79ad28ab7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 25 Feb 2022 14:27:54 +0100 Subject: [PATCH 126/800] Update build configurations for Rider to net6.0 paths --- .../.idea/runConfigurations/Benchmarks.xml | 6 +++--- .../.idea/runConfigurations/CatchRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/ManiaRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/OsuRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/TaikoRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/Tournament.xml | 6 +++--- .../.idea/runConfigurations/Tournament__Tests_.xml | 6 +++--- .idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml | 6 +++--- .../.idea/runConfigurations/osu___Tests_.xml | 6 +++--- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml index 498a710df9..d500c595c0 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml @@ -1,8 +1,8 @@ -