diff --git a/.editorconfig b/.editorconfig index 67c47000d3..c249e5e9b3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,9 @@ indent_style = space indent_size = 2 trim_trailing_whitespace = true +[g_*.cs] +generated_code = true + [*.cs] end_of_line = crlf insert_final_newline = true diff --git a/osu.Android.props b/osu.Android.props index fdec4e575b..270a0aa44b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -11,7 +11,7 @@ manifestmerger.jar - + diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index fe3e08537e..b1e11d7a60 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -187,7 +187,7 @@ namespace osu.Desktop return edit.BeatmapInfo.ToString() ?? string.Empty; case UserActivity.WatchingReplay watching: - return watching.BeatmapInfo.ToString(); + return watching.BeatmapInfo?.ToString() ?? string.Empty; case UserActivity.InLobby lobby: return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value; diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index efd3d358b7..a0db896f46 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -147,14 +147,12 @@ namespace osu.Desktop { base.SetHost(host); - var desktopWindow = (SDL2DesktopWindow)host.Window; - var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"); if (iconStream != null) - desktopWindow.SetIconFromStream(iconStream); + host.Window.SetIconFromStream(iconStream); - desktopWindow.CursorState |= CursorState.Hidden; - desktopWindow.Title = Name; + host.Window.CursorState |= CursorState.Hidden; + host.Window.Title = Name; } protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo(); diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs index 55b24b3ffa..f38b9b430e 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy lastDisplayedCombo = combo; - if (Time.Elapsed < 0) + if ((Clock as IGameplayClock)?.IsRewinding == true) { // needs more work to make rewind somehow look good. // basically we want the previous increment to play... or turning off RemoveCompletedTransforms (not feasible from a performance angle). diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 1b99270b65..567c288b47 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Catch.UI @@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Catch.UI comboDisplay.X = Catcher.X; - if (Time.Elapsed <= 0) + if ((Clock as IGameplayClock)?.IsRewinding == true) { // This is probably a wrong value, but currently the true value is not recorded. // Setting `true` will prevent generation of false-positive after-images (with more false-negatives). diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index a8563d65c4..c3fec92b92 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -298,7 +299,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return false; // do not run any of this logic when rewinding, as it inverts order of presses/releases. - if (Time.Elapsed < 0) + if ((Clock as IGameplayClock)?.IsRewinding == true) return false; if (CheckHittable?.Invoke(this, Time.Current) == false) @@ -337,7 +338,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return; // do not run any of this logic when rewinding, as it inverts order of presses/releases. - if (Time.Elapsed < 0) + if ((Clock as IGameplayClock)?.IsRewinding == true) return; Tail.UpdateResult(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index e1766adc20..d06fb5b4de 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -14,6 +14,7 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -179,16 +180,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Vector2? lastPosition; - private bool rewinding; - public void UpdateProgress(double completionProgress) { Position = drawableSlider.HitObject.CurvePositionAt(completionProgress); var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f); - if (Clock.ElapsedFrameTime != 0) - rewinding = Clock.ElapsedFrameTime < 0; + bool rewinding = (Clock as IGameplayClock)?.IsRewinding == true; // Ensure the value is substantially high enough to allow for Atan2 to get a valid angle. if (diff.LengthFast < 0.01f) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index a193bacde5..ac4462c18b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss; // TODO: The detection of rulesets is temporary until the leftover old skills have been reworked. - bool isConvert = score.BeatmapInfo.Ruleset.OnlineID != 1; + bool isConvert = score.BeatmapInfo!.Ruleset.OnlineID != 1; double multiplier = 1.13; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs index 37eb95b86f..5516e025cd 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -8,6 +9,7 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; @@ -26,11 +28,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private Bindable currentCombo { get; } = new BindableInt(); private int animationFrame; - private double beatLength; // required for editor blueprints (not sure why these circle pieces are zero size). public override Quad ScreenSpaceDrawQuad => backgroundLayer.ScreenSpaceDrawQuad; + private TimingControlPoint timingPoint = TimingControlPoint.DEFAULT; + public LegacyCirclePiece() { RelativeSizeAxes = Axes.Both; @@ -39,11 +42,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy [Resolved(canBeNull: true)] private GameplayState? gameplayState { get; set; } - [Resolved(canBeNull: true)] - private IBeatSyncProvider? beatSyncProvider { get; set; } - [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableHitObject drawableHitObject) + private void load(ISkinSource skin, DrawableHitObject drawableHitObject, IBeatSyncProvider? beatSyncProvider) { Drawable? getDrawableFor(string lookup) { @@ -64,6 +64,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (foregroundLayer != null) AddInternal(foregroundLayer); + drawableHitObject.StartTimeBindable.BindValueChanged(startTime => + { + timingPoint = beatSyncProvider?.ControlPoints?.TimingPointAt(startTime.NewValue) ?? TimingControlPoint.DEFAULT; + }, true); + // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). // For now just stop at first frame for sanity. foreach (var c in InternalChildren) @@ -115,14 +120,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy return; } - if (beatSyncProvider?.ControlPoints != null) - { - beatLength = beatSyncProvider.ControlPoints.TimingPointAt(Time.Current).BeatLength; - - animationFrame = Time.Current % ((beatLength * 2) / multiplier) >= beatLength / multiplier ? 0 : 1; - - animatableForegroundLayer.GotoFrame(animationFrame); - } + animationFrame = Math.Abs(Time.Current - timingPoint.Time) % ((timingPoint.BeatLength * 2) / multiplier) >= timingPoint.BeatLength / multiplier ? 0 : 1; + animatableForegroundLayer.GotoFrame(animationFrame); } private Color4 accentColour; diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs index 89b8c8927d..237fe758b5 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestCachedRetrievalWithFiles() => AddStep("run test", () => { - var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach()); + var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID)!.Detach()); Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0)); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestForcedRefetchRetrievalWithFiles() => AddStep("run test", () => { - var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach()); + var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID)!.Detach()); Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0)); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestSavePreservesCollections() => AddStep("run test", () => { - var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach()); + var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID)!.Detach()); var working = beatmaps.GetWorkingBeatmap(beatmap); diff --git a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs index ddb60606ec..c876316be4 100644 --- a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Database { return Realm.Run(r => { - var beatmapSetInfo = r.Find(importedSet.ID); + var beatmapSetInfo = r.Find(importedSet.ID)!; return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); }); }); @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Database { Realm.Write(r => { - var beatmapSetInfo = r.Find(importedSet.ID); + var beatmapSetInfo = r.Find(importedSet.ID)!; foreach (var b in beatmapSetInfo.Beatmaps) b.StarRating = -1; }); @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Database { return Realm.Run(r => { - var beatmapSetInfo = r.Find(importedSet.ID); + var beatmapSetInfo = r.Find(importedSet.ID)!; return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); }); }); @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Database { return Realm.Run(r => { - var beatmapSetInfo = r.Find(importedSet.ID); + var beatmapSetInfo = r.Find(importedSet.ID)!; return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); }); }); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Database { Realm.Write(r => { - var beatmapSetInfo = r.Find(importedSet.ID); + var beatmapSetInfo = r.Find(importedSet.ID)!; foreach (var b in beatmapSetInfo.Beatmaps) b.StarRating = -1; }); @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Database { return Realm.Run(r => { - var beatmapSetInfo = r.Find(importedSet.ID); + var beatmapSetInfo = r.Find(importedSet.ID)!; return beatmapSetInfo.Beatmaps.All(b => b.StarRating == -1); }); }); @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Database { return Realm.Run(r => { - var beatmapSetInfo = r.Find(importedSet.ID); + var beatmapSetInfo = r.Find(importedSet.ID)!; return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); }); }); diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 84e6a6c00f..0eac70f9c8 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -417,6 +417,60 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImport_Modify_Revert() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var store = new RealmRulesetStore(realm, storage); + + var imported = await LoadOszIntoStore(importer, realm.Realm); + + await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First()); + + var score = realm.Run(r => r.All().Single()); + + string originalHash = imported.Beatmaps.First().Hash; + const string modified_hash = "new_hash"; + + Assert.That(imported.Beatmaps.First().Scores.Single(), Is.EqualTo(score)); + + Assert.That(score.BeatmapHash, Is.EqualTo(originalHash)); + Assert.That(score.BeatmapInfo, Is.EqualTo(imported.Beatmaps.First())); + + // imitate making local changes via editor + // ReSharper disable once MethodHasAsyncOverload + realm.Write(r => + { + BeatmapInfo beatmap = imported.Beatmaps.First(); + beatmap.Hash = modified_hash; + beatmap.ResetOnlineInfo(); + beatmap.UpdateLocalScores(r); + }); + + Assert.That(!imported.Beatmaps.First().Scores.Any()); + + Assert.That(score.BeatmapInfo, Is.Null); + Assert.That(score.BeatmapHash, Is.EqualTo(originalHash)); + + // imitate reverting the local changes made above + // ReSharper disable once MethodHasAsyncOverload + realm.Write(r => + { + BeatmapInfo beatmap = imported.Beatmaps.First(); + beatmap.Hash = originalHash; + beatmap.ResetOnlineInfo(); + beatmap.UpdateLocalScores(r); + }); + + Assert.That(imported.Beatmaps.First().Scores.Single(), Is.EqualTo(score)); + + Assert.That(score.BeatmapHash, Is.EqualTo(originalHash)); + Assert.That(score.BeatmapInfo, Is.EqualTo(imported.Beatmaps.First())); + }); + } + [Test] public void TestImport_ThenModifyMapWithScore_ThenImport() { @@ -431,19 +485,19 @@ namespace osu.Game.Tests.Database await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First()); + Assert.That(imported.Beatmaps.First().Scores.Any()); + // imitate making local changes via editor // ReSharper disable once MethodHasAsyncOverload - realm.Write(_ => + realm.Write(r => { BeatmapInfo beatmap = imported.Beatmaps.First(); beatmap.Hash = "new_hash"; beatmap.ResetOnlineInfo(); + beatmap.UpdateLocalScores(r); }); - // for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap. - // the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (see: https://github.com/ppy/osu/pull/22539). - // TODO: revisit when fixing https://github.com/ppy/osu/issues/24069. - Assert.That(imported.Beatmaps.First().Scores.Any()); + Assert.That(!imported.Beatmaps.First().Scores.Any()); var importedSecondTime = await importer.Import(new ImportTask(temp)); @@ -461,6 +515,7 @@ namespace osu.Game.Tests.Database Assert.That(importedFirstTimeBeatmap.Hash != importedSecondTimeBeatmap.Hash); Assert.That(!importedFirstTimeBeatmap.Scores.Any()); Assert.That(importedSecondTimeBeatmap.Scores.Count() == 1); + Assert.That(importedSecondTimeBeatmap.Scores.Single().BeatmapInfo, Is.EqualTo(importedSecondTimeBeatmap)); }); } diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 83cb54df3f..d30b3c089e 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Database var beatmapInfo = s.Beatmaps.First(b => b.File?.Filename != removedFilename); scoreTargetBeatmapHash = beatmapInfo.Hash; - s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); + s.Realm!.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); realm.Run(r => r.Refresh()); @@ -372,7 +372,7 @@ namespace osu.Game.Tests.Database scoreTargetBeatmapHash = beatmapInfo.Hash; - s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); + s.Realm!.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); // locally modify beatmap @@ -383,14 +383,14 @@ namespace osu.Game.Tests.Database beatmapInfo.Hash = new_beatmap_hash; beatmapInfo.ResetOnlineInfo(); + beatmapInfo.UpdateLocalScores(s.Realm!); }); realm.Run(r => r.Refresh()); - // for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap. - // the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (https://github.com/ppy/osu/pull/22539). - // TODO: revisit when fixing https://github.com/ppy/osu/issues/24069. + // making changes to a beatmap doesn't remove the score from realm, but should disassociate the beatmap. checkCount(realm, 1); + Assert.That(realm.Run(r => r.All().First().BeatmapInfo), Is.Null); // reimport the original beatmap before local modifications var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOnlineCopy), importBeforeUpdate.Value); @@ -435,7 +435,7 @@ namespace osu.Game.Tests.Database { var beatmapInfo = s.Beatmaps.Last(); scoreTargetFilename = beatmapInfo.File?.Filename; - s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); + s.Realm!.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); realm.Run(r => r.Refresh()); @@ -528,7 +528,7 @@ namespace osu.Game.Tests.Database importBeforeUpdate.PerformWrite(s => { - var beatmapCollection = s.Realm.Add(new BeatmapCollection("test collection")); + var beatmapCollection = s.Realm!.Add(new BeatmapCollection("test collection")); beatmapsToAddToCollection = s.Beatmaps.Count - (allOriginalBeatmapsInCollection ? 0 : 1); for (int i = 0; i < beatmapsToAddToCollection; i++) @@ -543,7 +543,7 @@ namespace osu.Game.Tests.Database importAfterUpdate.PerformRead(updated => { - updated.Realm.Refresh(); + updated.Realm!.Refresh(); string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); @@ -593,7 +593,7 @@ namespace osu.Game.Tests.Database importBeforeUpdate.PerformWrite(s => { - var beatmapCollection = s.Realm.Add(new BeatmapCollection("test collection")); + var beatmapCollection = s.Realm!.Add(new BeatmapCollection("test collection")); originalHash = s.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash; beatmapCollection.BeatmapMD5Hashes.Add(originalHash); @@ -607,7 +607,7 @@ namespace osu.Game.Tests.Database importAfterUpdate.PerformRead(updated => { - updated.Realm.Refresh(); + updated.Realm!.Refresh(); string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); string updatedHash = updated.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash; diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index fd0b391d0d..b8073a65bc 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Database realm.RegisterCustomSubscription(r => { - var subscription = r.All().QueryAsyncWithNotifications((_, _, _) => + var subscription = r.All().QueryAsyncWithNotifications((_, _) => { realm.Run(_ => { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index d853e75db0..cea30acf3f 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -355,7 +355,7 @@ namespace osu.Game.Tests.Database return null; }); - void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) + void gotChange(IRealmCollection sender, ChangeSet? changes) { changesTriggered++; } diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 4ee302bbd0..45842a952a 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -54,7 +53,7 @@ namespace osu.Game.Tests.Database registration.Dispose(); }); - void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) + void onChanged(IRealmCollection sender, ChangeSet? changes) { lastChanges = changes; @@ -92,7 +91,7 @@ namespace osu.Game.Tests.Database registration.Dispose(); }); - void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) => lastChanges = changes; + void onChanged(IRealmCollection sender, ChangeSet? changes) => lastChanges = changes; } [Test] @@ -185,7 +184,7 @@ namespace osu.Game.Tests.Database } }); - void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) + void onChanged(IRealmCollection sender, ChangeSet? changes) { if (changes == null) resolvedItems = sender; diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index a5662fa121..8b4c6e2411 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -76,12 +76,12 @@ namespace osu.Game.Tests.Database Available = true, })); - Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.True); + Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.True); // Availability is updated on construction of a RealmRulesetStore var _ = new RealmRulesetStore(realm, storage); - Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.False); + Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.False); }); } @@ -101,18 +101,18 @@ namespace osu.Game.Tests.Database Available = true, })); - Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.True); + Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.True); // Availability is updated on construction of a RealmRulesetStore var _ = new RealmRulesetStore(realm, storage); - Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.False); + Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.False); // Simulate the ruleset getting updated LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION; var __ = new RealmRulesetStore(realm, storage); - Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.True); + Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.True); }); } diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index f4467867db..e2774cef00 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Database realm.Run(innerRealm => { - var binding = innerRealm.ResolveReference(tsr); + var binding = innerRealm.ResolveReference(tsr)!; innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); }); diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs index d585a0eb9f..b5303e1dd6 100644 --- a/osu.Game.Tests/Models/DisplayStringTest.cs +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -87,10 +87,10 @@ namespace osu.Game.Tests.Models var mock = new Mock(); mock.Setup(m => m.User).Returns(new APIUser { Username = "user" }); // TODO: temporary. - mock.Setup(m => m.Beatmap.Metadata.Artist).Returns("artist"); - mock.Setup(m => m.Beatmap.Metadata.Title).Returns("title"); - mock.Setup(m => m.Beatmap.Metadata.Author.Username).Returns("author"); - mock.Setup(m => m.Beatmap.DifficultyName).Returns("difficulty"); + mock.Setup(m => m.Beatmap!.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Beatmap!.Metadata.Title).Returns("title"); + mock.Setup(m => m.Beatmap!.Metadata.Author.Username).Returns("author"); + mock.Setup(m => m.Beatmap!.DifficultyName).Returns("difficulty"); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("user playing artist - title (author) [difficulty]")); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index 59c9c4587a..0f16d3f394 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); - AddStep("Add trigger source", () => Player.GameplayClockContainer.Add(sampleTriggerSource = new TestGameplaySampleTriggerSource(Player.DrawableRuleset.Playfield.HitObjectContainer))); + AddStep("Add trigger source", () => Player.DrawableRuleset.FrameStableComponents.Add(sampleTriggerSource = new TestGameplaySampleTriggerSource(Player.DrawableRuleset.Playfield.HitObjectContainer))); } [Test] @@ -153,6 +153,14 @@ namespace osu.Game.Tests.Visual.Gameplay waitForAliveObjectIndex(2); checkValidObjectIndex(2); + // test rewinding + seekBeforeIndex(1); + waitForAliveObjectIndex(1); + checkValidObjectIndex(1); + + seekBeforeIndex(1, 400); + checkValidObjectIndex(0); + seekBeforeIndex(3); waitForAliveObjectIndex(3); checkValidObjectIndex(3); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 80c4e4bce9..b0b9d48cbe 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -131,7 +131,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("results screen score has matching", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.EqualTo(playerMods.First())); AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); - AddUntilStep("databased score has correct mods", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID)).Mods.First(), () => Is.EqualTo(playerMods.First())); + AddUntilStep("databased score has correct mods", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID))!.Mods.First(), () => Is.EqualTo(playerMods.First())); } [Test] diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index 3004cb8a0c..0f17b08b7b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("show excess mods score", () => { var score = TestResources.CreateTestScoreInfo(); - score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray(); + score.Mods = score.BeatmapInfo!.Ruleset.CreateInstance().CreateAllMods().ToArray(); showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), score); }); } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index c05774400f..d71c72f4ec 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Ranking var author = new RealmUser { Username = "mapper_name" }; var score = TestResources.CreateTestScoreInfo(createTestBeatmap(author)); - score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray(); + score.Mods = score.BeatmapInfo!.Ruleset.CreateInstance().CreateAllMods().ToArray(); showPanel(score); }); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 42068ff117..c5b61c1a90 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -405,7 +405,7 @@ namespace osu.Game.Tests.Visual.Ranking public UnrankedSoloResultsScreen(ScoreInfo score) : base(score, true) { - Score.BeatmapInfo.OnlineID = 0; + Score.BeatmapInfo!.OnlineID = 0; Score.BeatmapInfo.Status = BeatmapOnlineStatus.Pending; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index af3a6e178c..0bff40f258 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Key(Key.Enter); }); - AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddAssert("ensure selection changed", () => selected != Beatmap.Value); } @@ -186,7 +186,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Key(Key.Down); }); - AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); } @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Key(Key.Enter); }); - AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddAssert("ensure selection changed", () => selected != Beatmap.Value); } @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.ReleaseButton(MouseButton.Left); }); - AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); } @@ -257,7 +257,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddStep("return", () => songSelect!.MakeCurrent()); AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); @@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); @@ -292,7 +292,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddStep("update beatmap", () => { @@ -1011,7 +1011,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); }); - AddUntilStep("wait for results screen presented", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap())); AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); @@ -1040,7 +1040,7 @@ namespace osu.Game.Tests.Visual.SongSelect songSelect!.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap())); }); - AddUntilStep("wait for results screen presented", () => !songSelect!.IsCurrentScreen()); + waitForDismissed(); AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap())); AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); @@ -1161,6 +1161,8 @@ namespace osu.Game.Tests.Visual.SongSelect rulesets.Dispose(); } + private void waitForDismissed() => AddUntilStep("wait for not current", () => !songSelect.AsNonNull().IsCurrentScreen()); + private partial class TestSongSelect : PlaySongSelect { public Action? StartRequested; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs index 2d54a4e566..bf6d8e524f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.UserInterface var testPresets = createTestPresets(); foreach (var preset in testPresets) - preset.Ruleset = realm.Find(preset.Ruleset.ShortName); + preset.Ruleset = realm.Find(preset.Ruleset.ShortName)!; realm.Add(testPresets); }); @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.UserInterface new ManiaModNightcore(), new ManiaModHardRock() }, - Ruleset = r.Find("mania") + Ruleset = r.Find("mania")! }))); AddUntilStep("2 panels visible", () => this.ChildrenOfType().Count() == 2); @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.UserInterface new OsuModHidden(), new OsuModHardRock() }, - Ruleset = r.Find("osu") + Ruleset = r.Find("osu")! }))); AddUntilStep("2 panels visible", () => this.ChildrenOfType().Count() == 2); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index dcb1f730a2..4cb6899ebc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Name = "AR0", Description = "Too... many... circles...", - Ruleset = r.Find(OsuRuleset.SHORT_NAME), + Ruleset = r.Find(OsuRuleset.SHORT_NAME)!, Mods = new[] { new OsuModDifficultyAdjust @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Name = "Half Time 0.5x", Description = "Very slow", - Ruleset = r.Find(OsuRuleset.SHORT_NAME), + Ruleset = r.Find(OsuRuleset.SHORT_NAME)!, Mods = new[] { new OsuModHalfTime diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 3cd5daf7a1..4d3ae079e3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -52,6 +52,32 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"unread count: {count.NewValue}"; }; }); + [Test] + public void TestBasicFlow() + { + setState(Visibility.Visible); + AddStep(@"simple #1", sendHelloNotification); + AddStep(@"simple #2", sendAmazingNotification); + AddStep(@"progress #1", sendUploadProgress); + AddStep(@"progress #2", sendDownloadProgress); + + checkProgressingCount(2); + + setState(Visibility.Hidden); + + AddRepeatStep(@"add many simple", sendManyNotifications, 3); + + waitForCompletion(); + + AddStep(@"progress #3", sendUploadProgress); + + checkProgressingCount(1); + + checkDisplayedCount(33); + + waitForCompletion(); + } + [Test] public void TestForwardWithFlingRight() { @@ -411,32 +437,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait for update applied", () => applyUpdate); } - [Test] - public void TestBasicFlow() - { - setState(Visibility.Visible); - AddStep(@"simple #1", sendHelloNotification); - AddStep(@"simple #2", sendAmazingNotification); - AddStep(@"progress #1", sendUploadProgress); - AddStep(@"progress #2", sendDownloadProgress); - - checkProgressingCount(2); - - setState(Visibility.Hidden); - - AddRepeatStep(@"add many simple", sendManyNotifications, 3); - - waitForCompletion(); - - AddStep(@"progress #3", sendUploadProgress); - - checkProgressingCount(1); - - checkDisplayedCount(33); - - waitForCompletion(); - } - [Test] public void TestImportantWhileClosed() { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 7d0571dde0..29f0f4f356 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Input; @@ -94,7 +93,7 @@ namespace osu.Game.Tournament Task.Run(readBracket); } - private void readBracket() + private async Task readBracket() { try { @@ -102,7 +101,7 @@ namespace osu.Game.Tournament { using (Stream stream = storage.GetStream(BRACKET_FILENAME, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) - ladder = JsonConvert.DeserializeObject(sr.ReadToEnd(), new JsonPointConverter()); + ladder = JsonConvert.DeserializeObject(await sr.ReadToEndAsync().ConfigureAwait(false), new JsonPointConverter()); } ladder ??= new LadderInfo(); @@ -166,8 +165,8 @@ namespace osu.Game.Tournament } addedInfo |= addPlayers(); - addedInfo |= addRoundBeatmaps(); - addedInfo |= addSeedingBeatmaps(); + addedInfo |= await addRoundBeatmaps().ConfigureAwait(false); + addedInfo |= await addSeedingBeatmaps().ConfigureAwait(false); if (addedInfo) saveChanges(); @@ -233,7 +232,7 @@ namespace osu.Game.Tournament /// /// Add missing beatmap info based on beatmap IDs /// - private bool addRoundBeatmaps() + private async Task addRoundBeatmaps() { var beatmapsRequiringPopulation = ladder.Rounds .SelectMany(r => r.Beatmaps) @@ -246,7 +245,7 @@ namespace osu.Game.Tournament { var b = beatmapsRequiringPopulation[i]; - b.Beatmap = new TournamentBeatmap(beatmapCache.GetBeatmapAsync(b.ID).GetResultSafely() ?? new APIBeatmap()); + b.Beatmap = new TournamentBeatmap(await beatmapCache.GetBeatmapAsync(b.ID).ConfigureAwait(false) ?? new APIBeatmap()); updateLoadProgressMessage($"Populating round beatmaps ({i} / {beatmapsRequiringPopulation.Count})"); } @@ -257,7 +256,7 @@ namespace osu.Game.Tournament /// /// Add missing beatmap info based on beatmap IDs /// - private bool addSeedingBeatmaps() + private async Task addSeedingBeatmaps() { var beatmapsRequiringPopulation = ladder.Teams .SelectMany(r => r.SeedingResults) @@ -271,7 +270,7 @@ namespace osu.Game.Tournament { var b = beatmapsRequiringPopulation[i]; - b.Beatmap = new TournamentBeatmap(beatmapCache.GetBeatmapAsync(b.ID).GetResultSafely() ?? new APIBeatmap()); + b.Beatmap = new TournamentBeatmap(await beatmapCache.GetBeatmapAsync(b.ID).ConfigureAwait(false) ?? new APIBeatmap()); updateLoadProgressMessage($"Populating seeding beatmaps ({i} / {beatmapsRequiringPopulation.Count})"); } diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 9fe3a41b03..9f62dae1f5 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -102,7 +102,7 @@ namespace osu.Game } } - r.Find(ruleset.ShortName).LastAppliedDifficultyVersion = currentVersion; + r.Find(ruleset.ShortName)!.LastAppliedDifficultyVersion = currentVersion; }); Logger.Log($"Finished resetting {countReset} beatmap sets for {ruleset.Name}"); @@ -163,8 +163,12 @@ namespace osu.Game { foreach (var score in r.All()) { - if (score.Statistics.Sum(kvp => kvp.Value) > 0 && score.MaximumStatistics.Sum(kvp => kvp.Value) == 0) + if (score.BeatmapInfo != null + && score.Statistics.Sum(kvp => kvp.Value) > 0 + && score.MaximumStatistics.Sum(kvp => kvp.Value) == 0) + { scoreIds.Add(score.ID); + } } }); @@ -184,7 +188,7 @@ namespace osu.Game // ReSharper disable once MethodHasAsyncOverload realmAccess.Write(r => { - r.Find(id).MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics); + r.Find(id)!.MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics); }); Logger.Log($"Populated maximum statistics for score {id}"); @@ -204,7 +208,9 @@ namespace osu.Game { Logger.Log("Querying for scores that need total score conversion..."); - HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All().Where(s => s.TotalScoreVersion == 30000002).AsEnumerable().Select(s => s.ID))); + HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All() + .Where(s => s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) + .AsEnumerable().Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); @@ -237,7 +243,7 @@ namespace osu.Game // ReSharper disable once MethodHasAsyncOverload realmAccess.Write(r => { - ScoreInfo s = r.Find(id); + ScoreInfo s = r.Find(id)!; s.TotalScore = newTotalScore; s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION; }); diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index fd766490fc..eadf7a3666 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -20,7 +20,6 @@ using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; -using osu.Game.Scoring; using Realms; namespace osu.Game.Beatmaps @@ -69,7 +68,7 @@ namespace osu.Game.Beatmaps Logger.Log($"Beatmap \"{updated}\" update completed successfully", LoggingTarget.Database); - original = realm.Find(original.ID); + original = realm!.Find(original.ID)!; // Generally the import process will do this for us if the OnlineIDs match, // but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated). @@ -210,8 +209,7 @@ namespace osu.Game.Beatmaps // Let's reattach any matching scores that exist in the database, based on hash. foreach (BeatmapInfo beatmap in model.Beatmaps) { - foreach (var score in realm.All().Where(score => score.BeatmapHash == beatmap.Hash)) - score.BeatmapInfo = beatmap; + beatmap.UpdateLocalScores(realm); } ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 5019d64276..c1aeec1f71 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -234,6 +234,22 @@ namespace osu.Game.Beatmaps } } + /// + /// Local scores are retained separate from a beatmap's lifetime, matched via . + /// Therefore we need to detach / reattach scores when a beatmap is edited or imported. + /// + /// A realm instance in an active write transaction. + public void UpdateLocalScores(Realm realm) + { + // first disassociate any scores which are already attached and no longer valid. + foreach (var score in Scores) + score.BeatmapInfo = null; + + // then attach any scores which match the new hash. + foreach (var score in realm.All().Where(s => s.BeatmapHash == Hash)) + score.BeatmapInfo = this; + } + IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 73811b2e62..54c243e842 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -208,7 +208,7 @@ namespace osu.Game.Beatmaps using (var transaction = r.BeginWrite()) { if (!beatmapInfo.IsManaged) - beatmapInfo = r.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID)!; beatmapInfo.Hidden = true; transaction.Commit(); @@ -227,7 +227,7 @@ namespace osu.Game.Beatmaps using (var transaction = r.BeginWrite()) { if (!beatmapInfo.IsManaged) - beatmapInfo = r.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID)!; beatmapInfo.Hidden = false; transaction.Commit(); @@ -330,7 +330,7 @@ namespace osu.Game.Beatmaps Realm.Write(r => { if (!beatmapInfo.IsManaged) - beatmapInfo = r.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID)!; Debug.Assert(beatmapInfo.BeatmapSet != null); Debug.Assert(beatmapInfo.File != null); @@ -460,13 +460,16 @@ namespace osu.Game.Beatmaps Realm.Write(r => { - var liveBeatmapSet = r.Find(setInfo.ID); + var liveBeatmapSet = r.Find(setInfo.ID)!; setInfo.CopyChangesToRealm(liveBeatmapSet); if (transferCollections) beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); + liveBeatmapSet.Beatmaps.Single(b => b.ID == beatmapInfo.ID) + .UpdateLocalScores(r); + // do not look up metadata. // this is a locally-modified set now, so looking up metadata is busy work at best and harmful at worst. ProcessBeatmap?.Invoke(liveBeatmapSet, MetadataLookupScope.None); diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 046adb8327..56bfdc5001 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps /// /// The managed beatmap set to update. A transaction will be opened to apply changes. /// The preferred scope to use for metadata lookup. - public void Process(BeatmapSetInfo beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) => beatmapSet.Realm.Write(r => + public void Process(BeatmapSetInfo beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) => beatmapSet.Realm!.Write(_ => { // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index e95565a5c8..e435992381 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -59,7 +59,7 @@ namespace osu.Game.Collections Current.BindValueChanged(selectionChanged); } - private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) + private void collectionsChanged(IRealmCollection collections, ChangeSet? changes) { var selectedItem = SelectedItem?.Value?.Collection; diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 0fdf196c4a..6fe38a3229 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -41,7 +41,7 @@ namespace osu.Game.Collections realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged); } - private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) + private void collectionsChanged(IRealmCollection collections, ChangeSet? changes) { Items.Clear(); Items.AddRange(collections.AsEnumerable().Select(c => c.ToLive(realm))); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 0ab0ff520d..4131148f3f 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -197,7 +197,7 @@ namespace osu.Game.Collections return true; } - private void deleteCollection() => collection.PerformWrite(c => c.Realm.Remove(c)); + private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c)); } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ba555a7926..edcbb94368 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -129,6 +129,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.KeyOverlay, false); + SetDefault(OsuSetting.ReplaySettingsOverlay, true); SetDefault(OsuSetting.GameplayLeaderboard, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); @@ -382,6 +383,7 @@ namespace osu.Game.Configuration SafeAreaConsiderations, ComboColourNormalisationAmount, ProfileCoverExpanded, - EditorLimitedDistanceSnap + EditorLimitedDistanceSnap, + ReplaySettingsOverlay } } diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs index 7db946d79f..02dfa50fe5 100644 --- a/osu.Game/Database/EmptyRealmSet.cs +++ b/osu.Game/Database/EmptyRealmSet.cs @@ -19,8 +19,8 @@ namespace osu.Game.Database IEnumerator IEnumerable.GetEnumerator() => emptySet.GetEnumerator(); public int Count => emptySet.Count; public T this[int index] => emptySet[index]; - public int IndexOf(object item) => emptySet.IndexOf((T)item); - public bool Contains(object item) => emptySet.Contains((T)item); + public int IndexOf(object? item) => item == null ? -1 : emptySet.IndexOf((T)item); + public bool Contains(object? item) => item != null && emptySet.Contains((T)item); public event NotifyCollectionChangedEventHandler? CollectionChanged { diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 7d1dc5239a..47feb8a8f9 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -34,13 +34,13 @@ namespace osu.Game.Database } public void DeleteFile(TModel item, RealmNamedFileUsage file) => - performFileOperation(item, managed => DeleteFile(managed, managed.Files.First(f => f.Filename == file.Filename), managed.Realm)); + performFileOperation(item, managed => DeleteFile(managed, managed.Files.First(f => f.Filename == file.Filename), managed.Realm!)); public void ReplaceFile(TModel item, RealmNamedFileUsage file, Stream contents) => - performFileOperation(item, managed => ReplaceFile(file, contents, managed.Realm)); + performFileOperation(item, managed => ReplaceFile(file, contents, managed.Realm!)); public void AddFile(TModel item, Stream contents, string filename) => - performFileOperation(item, managed => AddFile(managed, contents, filename, managed.Realm)); + performFileOperation(item, managed => AddFile(managed, contents, filename, managed.Realm!)); private void performFileOperation(TModel item, Action operation) { @@ -178,13 +178,14 @@ namespace osu.Game.Database // (ie. if an async import finished very recently). return Realm.Write(realm => { - if (!item.IsManaged) - item = realm.Find(item.ID); + TModel? processableItem = item; + if (!processableItem.IsManaged) + processableItem = realm.Find(item.ID); - if (item?.DeletePending != false) + if (processableItem?.DeletePending != false) return false; - item.DeletePending = true; + processableItem.DeletePending = true; return true; }); } @@ -195,13 +196,14 @@ namespace osu.Game.Database // (ie. if an async import finished very recently). Realm.Write(realm => { - if (!item.IsManaged) - item = realm.Find(item.ID); + TModel? processableItem = item; + if (!processableItem.IsManaged) + processableItem = realm.Find(item.ID); - if (item?.DeletePending != true) + if (processableItem?.DeletePending != true) return; - item.DeletePending = false; + processableItem.DeletePending = false; }); } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2bc932f307..f9f11c49ff 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -535,7 +535,7 @@ namespace osu.Game.Database lock (notificationsResetMap) { // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null)); } return RegisterCustomSubscription(action); @@ -755,10 +755,10 @@ namespace osu.Game.Database for (int i = 0; i < itemCount; i++) { - dynamic? oldItem = oldItems.ElementAt(i); - dynamic? newItem = newItems.ElementAt(i); + dynamic oldItem = oldItems.ElementAt(i); + dynamic newItem = newItems.ElementAt(i); - long? nullableOnlineID = oldItem?.OnlineID; + long? nullableOnlineID = oldItem.OnlineID; newItem.OnlineID = (int)(nullableOnlineID ?? -1); } } @@ -795,7 +795,7 @@ namespace osu.Game.Database for (int i = 0; i < metadataCount; i++) { - dynamic? oldItem = oldMetadata.ElementAt(i); + dynamic oldItem = oldMetadata.ElementAt(i); var newItem = newMetadata.ElementAt(i); string username = oldItem.Author; @@ -818,7 +818,7 @@ namespace osu.Game.Database for (int i = 0; i < newSettings.Count; i++) { - dynamic? oldItem = oldSettings.ElementAt(i); + dynamic oldItem = oldSettings.ElementAt(i); var newItem = newSettings.ElementAt(i); long rulesetId = oldItem.RulesetID; @@ -843,7 +843,7 @@ namespace osu.Game.Database for (int i = 0; i < newKeyBindings.Count; i++) { - dynamic? oldItem = oldKeyBindings.ElementAt(i); + dynamic oldItem = oldKeyBindings.ElementAt(i); var newItem = newKeyBindings.ElementAt(i); if (oldItem.RulesetID == null) @@ -897,7 +897,7 @@ namespace osu.Game.Database var scores = migration.NewRealm.All(); foreach (var score in scores) - score.BeatmapHash = score.BeatmapInfo.Hash; + score.BeatmapHash = score.BeatmapInfo?.Hash ?? string.Empty; break; } diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 9c871a3929..509fabec59 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using osu.Framework.Development; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Statistics; using Realms; @@ -104,7 +105,7 @@ namespace osu.Game.Database PerformRead(t => { - using (var transaction = t.Realm.BeginWrite()) + using (var transaction = t.Realm!.BeginWrite()) { perform(t); transaction.Commit(); @@ -133,7 +134,7 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - if (dataIsFromUpdateThread && !data.Realm.IsClosed) + if (dataIsFromUpdateThread && !data.Realm.AsNonNull().IsClosed) { RealmLiveStatistics.USAGE_UPDATE_IMMEDIATE.Value++; return; @@ -154,7 +155,7 @@ namespace osu.Game.Database // To ensure that behaviour matches what we'd expect (the object *is* available), force // a refresh to bring in any off-thread changes immediately. realm.Refresh(); - found = realm.Find(ID); + found = realm.Find(ID)!; } return found; diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 5a6c2e3232..72529ed9ff 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -43,7 +43,7 @@ namespace osu.Game.Database .ForMember(s => s.BeatmapSet, cc => cc.Ignore()) .AfterMap((s, d) => { - d.Ruleset = d.Realm.Find(s.Ruleset.ShortName); + d.Ruleset = d.Realm!.Find(s.Ruleset.ShortName)!; copyChangesToRealm(s.Difficulty, d.Difficulty); copyChangesToRealm(s.Metadata, d.Metadata); }); @@ -57,7 +57,7 @@ namespace osu.Game.Database // Importantly, search all of realm for the beatmap (not just the set's beatmaps). // It may have gotten detached, and if that's the case let's use this opportunity to fix // things up. - var existingBeatmap = d.Realm.Find(beatmap.ID); + var existingBeatmap = d.Realm!.Find(beatmap.ID); if (existingBeatmap != null) { @@ -77,7 +77,7 @@ namespace osu.Game.Database { ID = beatmap.ID, BeatmapSet = d, - Ruleset = d.Realm.Find(beatmap.Ruleset.ShortName) + Ruleset = d.Realm.Find(beatmap.Ruleset.ShortName)! }; d.Beatmaps.Add(newBeatmap); @@ -282,12 +282,10 @@ namespace osu.Game.Database /// /// A subscription token. It must be kept alive for as long as you want to receive change notifications. /// To stop receiving notifications, call . - /// - /// May be null in the case the provided collection is not managed. /// /// /// - public static IDisposable? QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) + public static IDisposable QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { if (!RealmAccess.CurrentThreadSubscriptionsAllowed) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index fab0be6cf0..be025e3aa2 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, _, _) => + realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, _) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 64268c73d0..01c454e3f9 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -129,6 +129,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.MouseMiddle, GlobalAction.TogglePauseReplay), new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward), new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward), + new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.ToggleReplaySettings), }; public IEnumerable SongSelectKeyBindings => new[] @@ -374,5 +375,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ExportReplay))] ExportReplay, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleReplaySettings))] + ToggleReplaySettings, } } diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 40f39d927d..f52f6abb89 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -65,10 +65,15 @@ namespace osu.Game.Localisation public static LocalisableString HUDVisibilityMode => new TranslatableString(getKey(@"hud_visibility_mode"), @"HUD overlay visibility mode"); /// - /// "Show health display even when you can't fail" + /// "Show health display even when you can't fail" /// public static LocalisableString ShowHealthDisplayWhenCantFail => new TranslatableString(getKey(@"show_health_display_when_cant_fail"), @"Show health display even when you can't fail"); + /// + /// "Show replay settings overlay" + /// + public static LocalisableString ShowReplaySettingsOverlay => new TranslatableString(getKey(@"show_replay_settings_overlay"), @"Show replay settings overlay"); + /// /// "Fade playfield to red when health is low" /// @@ -134,6 +139,6 @@ namespace osu.Game.Localisation /// public static LocalisableString ClassicScoreDisplay => new TranslatableString(getKey(@"classic_score_display"), @"Classic"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 9e53b23180..f93d86225c 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -324,6 +324,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus"); + /// + /// "Toggle replay settings" + /// + public static LocalisableString ToggleReplaySettings => new TranslatableString(getKey(@"toggle_replay_settings"), @"Toggle replay settings"); + /// /// "Save replay" /// diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index 44e440e8d9..53687f2b28 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -29,11 +29,6 @@ namespace osu.Game.Localisation /// public static LocalisableString ClearAll => new TranslatableString(getKey(@"clear_all"), @"Clear All"); - /// - /// "Cancel All" - /// - public static LocalisableString CancelAll => new TranslatableString(getKey(@"cancel_all"), @"Cancel All"); - /// /// "Your battery level is low! Charge your device to prevent interruptions during gameplay." /// diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 144c4445a3..3db602c353 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -40,7 +40,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, _, _) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, _) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index ae249d1b7f..65aac723da 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -171,6 +171,8 @@ namespace osu.Game.Online.Chat public abstract partial class HighlightMessageNotification : SimpleNotification { + public override string PopInSampleName => "UI/notification-mention"; + protected HighlightMessageNotification(Message message, Channel channel) { this.message = message; diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 331a471ad5..ceb8e53778 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -107,7 +107,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(_ => filteredBeatmaps(), (_, changes, _) => + realmSubscription = realm.RegisterForNotifications(_ => filteredBeatmaps(), (_, changes) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 4ddcb40368..de42292372 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || (!string.IsNullOrEmpty(s.Hash) && s.Hash == TrackedItem.Hash)) - && !s.DeletePending), (items, _, _) => + && !s.DeletePending), (items, _) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 89da8b9d32..14e137caf1 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -185,7 +185,7 @@ namespace osu.Game.Online.Spectator IsPlaying = true; // transfer state at point of beginning play - currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; + currentState.BeatmapID = score.ScoreInfo.BeatmapInfo!.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentState.State = SpectatedUserState.Playing; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 93dd97ea15..5b654e0c16 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -289,9 +289,9 @@ namespace osu.Game { base.SetHost(host); - if (host.Window is SDL2Window sdlWindow) + if (host.Window != null) { - sdlWindow.DragDrop += path => + host.Window.DragDrop += path => { // on macOS/iOS, URL associations are handled via SDL_DROPFILE events. if (path.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 425f40258e..615a3e39af 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -163,7 +163,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, username, #pragma warning disable 618 - new StatisticText(score.MaxCombo, score.BeatmapInfo.MaxCombo, @"0\x"), + new StatisticText(score.MaxCombo, score.BeatmapInfo!.MaxCombo, @"0\x"), #pragma warning restore 618 }; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index e030b1e34f..c92b79cb4d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -123,7 +123,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x"); - ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0; + ppColumn.Alpha = value.BeatmapInfo!.Status.GrantsPerformancePoints() ? 1 : 0; if (value.PP is double pp) ppColumn.Text = pp.ToLocalisableString(@"N0"); diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 9969677826..e1e5604e4c 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -225,7 +225,12 @@ namespace osu.Game.Overlays.Dialog /// /// Programmatically clicks the first button of the provided type. /// - public void PerformAction() where T : PopupDialogButton => Buttons.OfType().First().TriggerClick(); + public void PerformAction() where T : PopupDialogButton + { + // Buttons are regularly added in BDL or LoadComplete, so let's schedule to ensure + // they are ready to be pressed. + Schedule(() => Buttons.OfType().First().TriggerClick()); + } protected override bool OnKeyDown(KeyDownEvent e) { diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs index 75bc8fd3a8..385695f669 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs @@ -123,7 +123,7 @@ namespace osu.Game.Overlays.FirstRunSetup beatmapSubscription?.Dispose(); } - private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes, Exception error) => Schedule(() => + private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) => Schedule(() => { currentlyLoadedBeatmaps.Text = FirstRunSetupBeatmapScreenStrings.CurrentlyLoadedBeatmaps(sender.Count); diff --git a/osu.Game/Overlays/INotificationOverlay.cs b/osu.Game/Overlays/INotificationOverlay.cs index c5ff10c619..19c646a714 100644 --- a/osu.Game/Overlays/INotificationOverlay.cs +++ b/osu.Game/Overlays/INotificationOverlay.cs @@ -42,8 +42,8 @@ namespace osu.Game.Overlays IEnumerable AllNotifications { get; } /// - /// All ongoing operations (ie. any not in a completed state). + /// All ongoing operations (ie. any not in a completed or cancelled state). /// - public IEnumerable OngoingOperations => AllNotifications.OfType().Where(p => p.State != ProgressNotificationState.Completed && p.State != ProgressNotificationState.Cancelled); + public IEnumerable OngoingOperations => AllNotifications.OfType().Where(p => p.Ongoing); } } diff --git a/osu.Game/Overlays/Mods/AddPresetPopover.cs b/osu.Game/Overlays/Mods/AddPresetPopover.cs index d9e350e560..ef855f6166 100644 --- a/osu.Game/Overlays/Mods/AddPresetPopover.cs +++ b/osu.Game/Overlays/Mods/AddPresetPopover.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Mods Name = nameTextBox.Current.Value, Description = descriptionTextBox.Current.Value, Mods = selectedMods.Value.ToArray(), - Ruleset = r.Find(ruleset.Value.ShortName) + Ruleset = r.Find(ruleset.Value.ShortName)! })); this.HidePopover(); diff --git a/osu.Game/Overlays/Mods/ModPresetColumn.cs b/osu.Game/Overlays/Mods/ModPresetColumn.cs index bf5e576277..3b12eec195 100644 --- a/osu.Game/Overlays/Mods/ModPresetColumn.cs +++ b/osu.Game/Overlays/Mods/ModPresetColumn.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Mods private Task? latestLoadTask; internal bool ItemsLoaded => latestLoadTask?.IsCompleted == true; - private void asyncLoadPanels(IRealmCollection presets, ChangeSet changes, Exception error) + private void asyncLoadPanels(IRealmCollection presets, ChangeSet? changes) { cancellationTokenSource?.Cancel(); diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 43b9024303..7784643163 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -109,7 +109,7 @@ namespace osu.Game.Overlays.Music beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true); } - private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + private void beatmapsChanged(IRealmCollection sender, ChangeSet changes) { if (changes == null) { diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 21027b0931..c9d09848f8 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -108,8 +108,8 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Children = new[] { - new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(SimpleNotification) }, NotificationsStrings.ClearAll), - new NotificationSection(NotificationsStrings.RunningTasks, new[] { typeof(ProgressNotification) }, NotificationsStrings.CancelAll), + new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(SimpleNotification) }), + new NotificationSection(NotificationsStrings.RunningTasks, new[] { typeof(ProgressNotification) }), } } } @@ -169,7 +169,7 @@ namespace osu.Game.Overlays Logger.Log($"⚠️ {notification.Text}"); - notification.Closed += notificationClosed; + notification.Closed += () => notificationClosed(notification); if (notification is IHasCompletionTarget hasCompletionTarget) hasCompletionTarget.CompletionTarget = Post; @@ -229,17 +229,20 @@ namespace osu.Game.Overlays mainContent.FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.In); } - private void notificationClosed() => Schedule(() => + private void notificationClosed(Notification notification) => Schedule(() => { updateCounts(); // this debounce is currently shared between popin/popout sounds, which means one could potentially not play when the user is expecting it. // popout is constant across all notification types, and should therefore be handled using playback concurrency instead, but seems broken at the moment. - playDebouncedSample("UI/overlay-pop-out"); + playDebouncedSample(notification.PopOutSampleName); }); private void playDebouncedSample(string sampleName) { + if (string.IsNullOrEmpty(sampleName)) + return; + if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > OsuGameBase.SAMPLE_DEBOUNCE_TIME) { audio.Samples.Get(sampleName)?.Play(); diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 77d3317b1f..8cdc373417 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -50,7 +50,8 @@ namespace osu.Game.Overlays.Notifications /// public virtual bool DisplayOnTop => true; - public virtual string PopInSampleName => "UI/notification-pop-in"; + public virtual string PopInSampleName => "UI/notification-default"; + public virtual string PopOutSampleName => "UI/overlay-pop-out"; protected NotificationLight Light; diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 4e28ade802..10c2900d63 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -13,6 +13,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; using osuTK; namespace osu.Game.Overlays.Notifications @@ -38,15 +39,12 @@ namespace osu.Game.Overlays.Notifications public IEnumerable AcceptedNotificationTypes { get; } - private readonly LocalisableString clearButtonText; - private readonly LocalisableString titleText; - public NotificationSection(LocalisableString title, IEnumerable acceptedNotificationTypes, LocalisableString clearButtonText) + public NotificationSection(LocalisableString title, IEnumerable acceptedNotificationTypes) { AcceptedNotificationTypes = acceptedNotificationTypes.ToArray(); - this.clearButtonText = clearButtonText.ToUpper(); titleText = title; } @@ -75,7 +73,7 @@ namespace osu.Game.Overlays.Notifications { new ClearAllButton { - Text = clearButtonText, + Text = NotificationsStrings.ClearAll.ToUpper(), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Action = clearAll @@ -115,10 +113,11 @@ namespace osu.Game.Overlays.Notifications }); } - private void clearAll() + private void clearAll() => notifications.Children.ForEach(c => { - notifications.Children.ForEach(c => c.Close(true)); - } + if (c is not ProgressNotification p || !p.Ongoing) + c.Close(true); + }); protected override void Update() { diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index 46972d4b5e..93286d9d36 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -10,6 +10,8 @@ namespace osu.Game.Overlays.Notifications { public partial class ProgressCompletionNotification : SimpleNotification { + public override string PopInSampleName => "UI/notification-done"; + public ProgressCompletionNotification() { Icon = FontAwesome.Solid.Check; diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index e6662e2179..6ea032213e 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -4,6 +4,8 @@ using System; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -25,8 +27,15 @@ namespace osu.Game.Overlays.Notifications public Func? CancelRequested { get; set; } + /// + /// Whether the operation represented by the is still ongoing. + /// + public bool Ongoing => State != ProgressNotificationState.Completed && State != ProgressNotificationState.Cancelled; + protected override bool AllowFlingDismiss => false; + public override string PopOutSampleName => State is ProgressNotificationState.Cancelled ? base.PopOutSampleName : ""; + /// /// The function to post completion notifications back to. /// @@ -122,6 +131,7 @@ namespace osu.Game.Overlays.Notifications cancellationTokenSource.Cancel(); IconContent.FadeColour(ColourInfo.GradientVertical(Color4.Gray, Color4.Gray.Lighten(0.5f)), colour_fade_duration); + cancelSample?.Play(); loadingSpinner.Hide(); var icon = new SpriteIcon @@ -190,6 +200,8 @@ namespace osu.Game.Overlays.Notifications private LoadingSpinner loadingSpinner = null!; + private Sample? cancelSample; + private readonly TextFlowContainer textDrawable; public ProgressNotification() @@ -217,7 +229,7 @@ namespace osu.Game.Overlays.Notifications } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, AudioManager audioManager) { colourQueued = colours.YellowDark; colourActive = colours.Blue; @@ -236,6 +248,8 @@ namespace osu.Game.Overlays.Notifications Size = new Vector2(loading_spinner_size), } }); + + cancelSample = audioManager.Samples.Get(@"UI/notification-cancel"); } public override void Close(bool runFlingAnimation) diff --git a/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs b/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs index 758eea93d4..81e3b40ffc 100644 --- a/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs @@ -7,7 +7,7 @@ namespace osu.Game.Overlays.Notifications { public partial class SimpleErrorNotification : SimpleNotification { - public override string PopInSampleName => "UI/error-notification-pop-in"; + public override string PopInSampleName => "UI/notification-error"; public SimpleErrorNotification() { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index e7c83159cd..3e67b2f103 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -25,10 +25,9 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }, new SettingsCheckbox { - ClassicDefault = false, - LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail, - Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), - Keywords = new[] { "hp", "bar" } + LabelText = GameplaySettingsStrings.ShowReplaySettingsOverlay, + Current = config.GetBindable(OsuSetting.ReplaySettingsOverlay), + Keywords = new[] { "hide" }, }, new SettingsCheckbox { @@ -41,6 +40,13 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = GameplaySettingsStrings.AlwaysShowGameplayLeaderboard, Current = config.GetBindable(OsuSetting.GameplayLeaderboard), }, + new SettingsCheckbox + { + ClassicDefault = false, + LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail, + Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), + Keywords = new[] { "hp", "bar" } + }, }; } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index b04e514ec2..725925c8cf 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -440,7 +440,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } private void updateStoreFromButton(KeyButton button) => - realm.WriteAsync(r => r.Find(button.KeyBinding.ID).KeyCombinationString = button.KeyBinding.KeyCombinationString); + realm.WriteAsync(r => r.Find(button.KeyBinding.ID)!.KeyCombinationString = button.KeyBinding.KeyCombinationString); private void updateIsDefaultValue() { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 5382eac675..e997e70157 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Settings.Sections }); } - private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + private void skinsChanged(IRealmCollection sender, ChangeSet changes) { // This can only mean that realm is recycling, else we would see the protected skins. // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e4d8eb2335..bf649a0a15 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK.Graphics; @@ -688,7 +689,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected bool UpdateResult(bool userTriggered) { // It's possible for input to get into a bad state when rewinding gameplay, so results should not be processed - if (Time.Elapsed < 0) + if ((Clock as IGameplayClock)?.IsRewinding == true) return false; if (Judged) diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index 3644d099d9..d17558f800 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -28,7 +28,7 @@ namespace osu.Game.Scoring double? PP { get; } - IBeatmapInfo Beatmap { get; } + IBeatmapInfo? Beatmap { get; } IRulesetInfo Ruleset { get; } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index ef033bf5bd..6868c89d26 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -66,7 +66,7 @@ namespace osu.Game.Scoring.Legacy { sw.Write((byte)(score.ScoreInfo.Ruleset.OnlineID)); sw.Write(LATEST_VERSION); - sw.Write(score.ScoreInfo.BeatmapInfo.MD5Hash); + sw.Write(score.ScoreInfo.BeatmapInfo!.MD5Hash); sw.Write(score.ScoreInfo.User.Username); sw.Write(FormattableString.Invariant($"lazer-{score.ScoreInfo.User.Username}-{score.ScoreInfo.Date}").ComputeMD5Hash()); sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0)); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 5ada2a410d..81b9f57bbc 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -64,12 +64,14 @@ namespace osu.Game.Scoring protected override void Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { + Debug.Assert(model.BeatmapInfo != null); + // Ensure the beatmap is not detached. if (!model.BeatmapInfo.IsManaged) - model.BeatmapInfo = realm.Find(model.BeatmapInfo.ID); + model.BeatmapInfo = realm.Find(model.BeatmapInfo.ID)!; if (!model.Ruleset.IsManaged) - model.Ruleset = realm.Find(model.Ruleset.ShortName); + model.Ruleset = realm.Find(model.Ruleset.ShortName)!; // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). // Under no circumstance do we want these to be written to realm as null. @@ -101,10 +103,12 @@ namespace osu.Game.Scoring /// The score to populate the statistics of. public void PopulateMaximumStatistics(ScoreInfo score) { + Debug.Assert(score.BeatmapInfo != null); + if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0) return; - var beatmap = score.BeatmapInfo.Detach(); + var beatmap = score.BeatmapInfo!.Detach(); var ruleset = score.Ruleset.Detach(); var rulesetInstance = ruleset.CreateInstance(); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index ea9007f4dc..c6f4433824 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -35,9 +35,16 @@ namespace osu.Game.Scoring /// The this score was made against. /// /// - /// When setting this, make sure to also set to allow relational consistency when a beatmap is potentially changed. + /// + /// This property may be if the score was set on a beatmap (or a version of the beatmap) that is not available locally + /// e.g. due to online updates, or local modifications to the beatmap. + /// The property will only link to a if its matches . + /// + /// + /// Due to the above, whenever setting this, make sure to also set to allow relational consistency when a beatmap is potentially changed. + /// /// - public BeatmapInfo BeatmapInfo { get; set; } = null!; + public BeatmapInfo? BeatmapInfo { get; set; } /// /// The at the point in time when the score was set. @@ -150,14 +157,12 @@ namespace osu.Game.Scoring public int RankInt { get; set; } IRulesetInfo IScoreInfo.Ruleset => Ruleset; - IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; + IBeatmapInfo? IScoreInfo.Beatmap => BeatmapInfo; IUser IScoreInfo.User => User; IEnumerable IHasNamedFiles.Files => Files; #region Properties required to make things work with existing usages - public Guid BeatmapInfoID => BeatmapInfo.ID; - public int UserID => RealmUser.OnlineID; public int RulesetID => Ruleset.OnlineID; diff --git a/osu.Game/Scoring/ScoreInfoExtensions.cs b/osu.Game/Scoring/ScoreInfoExtensions.cs index 85598076d6..6e57a9fd0b 100644 --- a/osu.Game/Scoring/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/ScoreInfoExtensions.cs @@ -13,7 +13,7 @@ namespace osu.Game.Scoring /// /// A user-presentable display title representing this score. /// - public static string GetDisplayTitle(this IScoreInfo scoreInfo) => $"{scoreInfo.User.Username} playing {scoreInfo.Beatmap.GetDisplayTitle()}"; + public static string GetDisplayTitle(this IScoreInfo scoreInfo) => $"{scoreInfo.User.Username} playing {scoreInfo.Beatmap?.GetDisplayTitle() ?? "unknown"}"; /// /// Orders an array of s by total score. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 55bcb9f79d..31b5bd8365 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -141,7 +141,7 @@ namespace osu.Game.Scoring { Realm.Run(r => { - var beatmapScores = r.Find(beatmap.ID).Scores.ToList(); + var beatmapScores = r.Find(beatmap.ID)!.Scores.ToList(); Delete(beatmapScores, silent); }); } diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index bdbcfe4efe..1f2b1aeb95 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring { var score = lookup.ScoreInfo; - var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, token).ConfigureAwait(false); + var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, token).ConfigureAwait(false); // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. if (attributes?.Attributes == null) diff --git a/osu.Game/Screens/Play/ComboEffects.cs b/osu.Game/Screens/Play/ComboEffects.cs index 09c94a8f1d..6f12cfde64 100644 --- a/osu.Game/Screens/Play/ComboEffects.cs +++ b/osu.Game/Screens/Play/ComboEffects.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play if (gameplayClock.CurrentTime < firstBreakTime) firstBreakTime = null; - if (gameplayClock.ElapsedFrameTime < 0) + if (gameplayClock.IsRewinding) return; if (combo.NewValue == 0 && (combo.OldValue > 20 || (alwaysPlayFirst.Value && firstBreakTime == null))) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index b6b385e262..dbb0456cd0 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -3,10 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osuTK; using osu.Game.Screens.Play.PlayerSettings; -using osuTK.Input; namespace osu.Game.Screens.Play.HUD { @@ -14,16 +12,12 @@ namespace osu.Game.Screens.Play.HUD { private const int fade_duration = 200; - public bool ReplayLoaded; - public readonly PlaybackSettings PlaybackSettings; public readonly VisualSettings VisualSettings; public PlayerSettingsOverlay() { - AlwaysPresent = true; - Anchor = Anchor.TopRight; Origin = Anchor.TopRight; AutoSizeAxes = Axes.Both; @@ -37,8 +31,6 @@ namespace osu.Game.Screens.Play.HUD Spacing = new Vector2(0, 20), Children = new PlayerSettingsGroup[] { - //CollectionSettings = new CollectionSettings(), - //DiscussionSettings = new DiscussionSettings(), PlaybackSettings = new PlaybackSettings { Expanded = { Value = false } }, VisualSettings = new VisualSettings { Expanded = { Value = false } }, new AudioSettings { Expanded = { Value = false } } @@ -48,24 +40,5 @@ namespace osu.Game.Screens.Play.HUD protected override void PopIn() => this.FadeIn(fade_duration); protected override void PopOut() => this.FadeOut(fade_duration); - - // We want to handle keyboard inputs all the time in order to trigger ToggleVisibility() when not visible - public override bool PropagateNonPositionalInputSubTree => true; - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.Repeat) return false; - - if (e.ControlPressed) - { - if (e.Key == Key.H && ReplayLoaded) - { - ToggleVisibility(); - return true; - } - } - - return base.OnKeyDown(e); - } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f0a2975958..d11171e3fe 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -78,6 +78,7 @@ namespace osu.Game.Screens.Play public Bindable ShowHud { get; } = new BindableBool(); private Bindable configVisibilityMode; + private Bindable configSettingsOverlay; private readonly BindableBool replayLoaded = new BindableBool(); @@ -178,6 +179,7 @@ namespace osu.Game.Screens.Play ModDisplay.Current.Value = mods; configVisibilityMode = config.GetBindable(OsuSetting.HUDVisibilityMode); + configSettingsOverlay = config.GetBindable(OsuSetting.ReplaySettingsOverlay); if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce) { @@ -204,9 +206,24 @@ namespace osu.Game.Screens.Play holdingForHUD.BindValueChanged(_ => updateVisibility()); IsPlaying.BindValueChanged(_ => updateVisibility()); - configVisibilityMode.BindValueChanged(_ => updateVisibility(), true); + configVisibilityMode.BindValueChanged(_ => updateVisibility()); + configSettingsOverlay.BindValueChanged(_ => updateVisibility()); - replayLoaded.BindValueChanged(replayLoadedValueChanged, true); + replayLoaded.BindValueChanged(e => + { + if (e.NewValue) + { + ModDisplay.FadeIn(200); + InputCountController.Margin = new MarginPadding(10) { Bottom = 30 }; + } + else + { + ModDisplay.Delay(2000).FadeOut(200); + InputCountController.Margin = new MarginPadding(10); + } + + updateVisibility(); + }, true); } protected override void Update() @@ -280,6 +297,11 @@ namespace osu.Game.Screens.Play return; } + if (configSettingsOverlay.Value && replayLoaded.Value) + PlayerSettingsOverlay.Show(); + else + PlayerSettingsOverlay.Hide(); + switch (configVisibilityMode.Value) { case HUDVisibilityMode.Never: @@ -297,26 +319,6 @@ namespace osu.Game.Screens.Play } } - private void replayLoadedValueChanged(ValueChangedEvent e) - { - PlayerSettingsOverlay.ReplayLoaded = e.NewValue; - - if (e.NewValue) - { - PlayerSettingsOverlay.Show(); - ModDisplay.FadeIn(200); - InputCountController.Margin = new MarginPadding(10) { Bottom = 30 }; - } - else - { - PlayerSettingsOverlay.Hide(); - ModDisplay.Delay(2000).FadeOut(200); - InputCountController.Margin = new MarginPadding(10); - } - - updateVisibility(); - } - protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { if (drawableRuleset is ICanAttachHUDPieces attachTarget) @@ -354,6 +356,10 @@ namespace osu.Game.Screens.Play switch (e.Action) { + case GlobalAction.ToggleReplaySettings: + configSettingsOverlay.Value = !configSettingsOverlay.Value; + return true; + case GlobalAction.HoldForHUD: holdingForHUD.Value = true; return true; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b979fc2740..379c10a4a4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -11,8 +11,6 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -114,8 +112,6 @@ namespace osu.Game.Screens.Play private Ruleset ruleset; - private Sample sampleRestart; - public BreakOverlay BreakOverlay; /// @@ -195,7 +191,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game, CancellationToken cancellationToken) + private void load(OsuConfigManager config, OsuGameBase game, CancellationToken cancellationToken) { var gameplayMods = Mods.Value.Select(m => m.DeepClone()).ToArray(); @@ -213,8 +209,6 @@ namespace osu.Game.Screens.Play if (playableBeatmap == null) return; - sampleRestart = audio.Samples.Get(@"Gameplay/restart"); - mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); if (game != null) @@ -295,14 +289,17 @@ namespace osu.Game.Screens.Play if (Configuration.AllowRestart) { - rulesetSkinProvider.Add(new HotkeyRetryOverlay + rulesetSkinProvider.AddRange(new Drawable[] { - Action = () => + new HotkeyRetryOverlay { - if (!this.IsCurrentScreen()) return; + Action = () => + { + if (!this.IsCurrentScreen()) return; - fadeOut(true); - Restart(true); + fadeOut(true); + Restart(true); + }, }, }); } @@ -673,7 +670,6 @@ namespace osu.Game.Screens.Play // stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader. musicController.Stop(); - sampleRestart?.Play(); RestartRequested?.Invoke(quickRestart); PerformExit(false); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 30ae5ee5aa..4b15bac0f3 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Game.Audio; using osu.Game.Audio.Effects; using osu.Game.Configuration; using osu.Game.Graphics; @@ -25,6 +26,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Skinning; using osu.Game.Users; using osu.Game.Utils; using osuTK; @@ -76,6 +78,8 @@ namespace osu.Game.Screens.Play private AudioFilter lowPassFilter = null!; private AudioFilter highPassFilter = null!; + private SkinnableSound sampleRestart = null!; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); @@ -199,7 +203,8 @@ namespace osu.Game.Screens.Play }, idleTracker = new IdleTracker(750), lowPassFilter = new AudioFilter(audio.TrackMixer), - highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass) + highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass), + sampleRestart = new SkinnableSound(new SampleInfo(@"Gameplay/restart", @"pause-retry-click")) }; if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) @@ -265,6 +270,8 @@ namespace osu.Game.Screens.Play playerConsumed = false; cancelLoad(); + sampleRestart.Play(); + contentIn(); } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index dafdf00136..f7ae3eb62b 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play { IBeatmapInfo beatmap = score.ScoreInfo.BeatmapInfo; - Debug.Assert(beatmap.OnlineID > 0); + Debug.Assert(beatmap!.OnlineID > 0); return new SubmitSoloScoreRequest(score.ScoreInfo, token, beatmap.OnlineID); } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 82c429798e..d1dc1a81db 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Ranking.Expanded [BackgroundDependencyLoader] private void load(BeatmapDifficultyCache beatmapDifficultyCache) { - var beatmap = score.BeatmapInfo; + var beatmap = score.BeatmapInfo!; var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; string creator = metadata.Author.Username; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index b9f3b65129..96fed3e6ba 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -305,7 +305,7 @@ namespace osu.Game.Screens.Ranking float origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos).X; expandedPanel.MoveToX(origLocation) .Then() - .MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint); + .MoveToX(StatisticsPanel.SIDE_PADDING, 400, Easing.OutElasticQuarter); // Hide contracted panels. foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) @@ -313,7 +313,7 @@ namespace osu.Game.Screens.Ranking ScorePanelList.HandleInput = false; // Dim background. - ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.1f), 150)); + ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.4f), 400, Easing.OutQuint)); detachedPanel = expandedPanel; } @@ -329,7 +329,7 @@ namespace osu.Game.Screens.Ranking float origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos).X; detachedPanel.MoveToX(origLocation) .Then() - .MoveToX(0, 150, Easing.OutQuint); + .MoveToX(0, 250, Easing.OutElasticQuarter); // Show contracted panels. foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) @@ -337,7 +337,7 @@ namespace osu.Game.Screens.Ranking ScorePanelList.HandleInput = true; // Un-dim background. - ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.5f), 150)); + ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.5f), 250, Easing.OutQuint)); detachedPanel = null; } diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index c8920a734d..f187b8a302 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Ranking protected override APIRequest? FetchScores(Action>? scoresCallback) { - if (Score.BeatmapInfo.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) + if (Score.BeatmapInfo!.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index c36d7726dc..8b059efaf4 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -223,7 +223,7 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void PopIn() { - this.FadeIn(150, Easing.OutQuint); + this.FadeIn(350, Easing.OutQuint); popInSample?.Play(); wasOpened = true; @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void PopOut() { - this.FadeOut(150, Easing.OutQuint); + this.FadeOut(250, Easing.OutQuint); if (wasOpened) popOutSample?.Play(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3d87a57295..9af9a0ce72 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -223,7 +223,7 @@ namespace osu.Game.Screens.Select subscriptionHiddenBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => b.Hidden), beatmapsChanged); } - private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet? changes, Exception? error) + private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) { // If loading test beatmaps, avoid overwriting with realm subscription callbacks. if (loadedTestBeatmaps) @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Select removeBeatmapSet(sender[i].ID); } - private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes, Exception? error) + private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) { // If loading test beatmaps, avoid overwriting with realm subscription callbacks. if (loadedTestBeatmaps) @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Select foreach (var id in realmSets) { if (!root.BeatmapSetsByID.ContainsKey(id)) - UpdateBeatmapSet(realm.Realm.Find(id).Detach()); + UpdateBeatmapSet(realm.Realm.Find(id)!.Detach()); } foreach (var id in root.BeatmapSetsByID.Keys) @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Select } } - private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes, Exception? error) + private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) { // we only care about actual changes in hidden status. if (changes == null) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 5dd2486e1c..3605e3d706 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -76,14 +76,12 @@ namespace osu.Game.Screens.Select protected override void PopIn() { this.MoveToX(0, animation_duration, Easing.OutQuint); - this.RotateTo(0, animation_duration, Easing.OutQuint); this.FadeIn(transition_duration); } protected override void PopOut() { this.MoveToX(-100, animation_duration, Easing.In); - this.RotateTo(10, animation_duration, Easing.In); this.FadeOut(transition_duration * 2, Easing.In); } diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 7c632b63db..c17de77619 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select.Carousel localScoresChanged); }, true); - void localScoresChanged(IRealmCollection sender, ChangeSet? changes, Exception _) + void localScoresChanged(IRealmCollection sender, ChangeSet? changes) { // This subscription may fire from changes to linked beatmaps, which we don't care about. // It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications. diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 4c41ed3622..58c14b15b9 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Select.Leaderboards + $" AND {nameof(ScoreInfo.DeletePending)} == false" , beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); - void localScoresChanged(IRealmCollection sender, ChangeSet? changes, Exception exception) + void localScoresChanged(IRealmCollection sender, ChangeSet? changes) { if (cancellationToken.IsCancellationRequested) return; diff --git a/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs b/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs index c4add31a4f..cd98872b65 100644 --- a/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs +++ b/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs @@ -4,9 +4,7 @@ using osu.Framework.Allocation; using osu.Game.Overlays.Dialog; using osu.Game.Scoring; -using System.Diagnostics; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; namespace osu.Game.Screens.Select { @@ -20,11 +18,8 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(BeatmapManager beatmapManager, ScoreManager scoreManager) + private void load(ScoreManager scoreManager) { - BeatmapInfo? beatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == score.BeatmapInfoID); - Debug.Assert(beatmapInfo != null); - BodyText = $"{score.User} ({score.DisplayAccuracy}, {score.Rank})"; Icon = FontAwesome.Regular.TrashAlt; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 2b56767bd0..48b5c210b8 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Spectate })); } - private void beatmapsChanged(IRealmCollection items, ChangeSet changes, Exception ___) + private void beatmapsChanged(IRealmCollection items, ChangeSet changes) { if (changes?.InsertedIndices == null) return; diff --git a/osu.Game/Skinning/RealmBackedResourceStore.cs b/osu.Game/Skinning/RealmBackedResourceStore.cs index cc887a7a61..cce099a268 100644 --- a/osu.Game/Skinning/RealmBackedResourceStore.cs +++ b/osu.Game/Skinning/RealmBackedResourceStore.cs @@ -38,7 +38,7 @@ namespace osu.Game.Skinning realmSubscription?.Dispose(); } - private void skinChanged(IRealmCollection sender, ChangeSet changes, Exception error) => invalidateCache(); + private void skinChanged(IRealmCollection sender, ChangeSet? changes) => invalidateCache(); protected override IEnumerable GetFilenames(string name) { diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 43760c4a19..f2103a45c4 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -198,7 +198,7 @@ namespace osu.Game.Skinning using (var streamContent = new MemoryStream(Encoding.UTF8.GetBytes(skinInfoJson))) { - modelManager.AddFile(s, streamContent, skin_info_file, s.Realm); + modelManager.AddFile(s, streamContent, skin_info_file, s.Realm!); } // Then serialise each of the drawable component groups into respective files. @@ -213,9 +213,9 @@ namespace osu.Game.Skinning var oldFile = s.GetFile(filename); if (oldFile != null) - modelManager.ReplaceFile(oldFile, streamContent, s.Realm); + modelManager.ReplaceFile(oldFile, streamContent, s.Realm!); else - modelManager.AddFile(s, streamContent, filename, s.Realm); + modelManager.AddFile(s, streamContent, filename, s.Realm!); } } diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 16496ff320..37260b3b13 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Framework.Testing.Input; +using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; @@ -25,8 +26,7 @@ namespace osu.Game.Tests.Visual protected readonly ManualInputManager InputManager; - private readonly RoundedButton buttonTest; - private readonly RoundedButton buttonLocal; + private readonly Container takeControlOverlay; /// /// Whether to create a nested container to handle s that result from local (manual) test input. @@ -66,12 +66,12 @@ namespace osu.Game.Tests.Visual UseParentInput = true, Child = mainContent }, - new Container + takeControlOverlay = new Container { AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Margin = new MarginPadding(5), + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding(40), CornerRadius = 5, Masking = true, Children = new Drawable[] @@ -80,44 +80,38 @@ namespace osu.Game.Tests.Visual { Colour = Color4.Black, RelativeSizeAxes = Axes.Both, - Alpha = 0.5f, + Alpha = 0.4f, }, new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Margin = new MarginPadding(5), - Spacing = new Vector2(5), + Margin = new MarginPadding(10), + Spacing = new Vector2(10), Children = new Drawable[] { new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = "Input Priority" + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Text = "The test is currently overriding local input", }, new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Margin = new MarginPadding(5), Spacing = new Vector2(5), Direction = FillDirection.Horizontal, Children = new Drawable[] { - buttonLocal = new RoundedButton + new RoundedButton { - Text = "local", - Size = new Vector2(50, 30), - Action = returnUserInput - }, - buttonTest = new RoundedButton - { - Text = "test", - Size = new Vector2(50, 30), - Action = returnTestInput + Text = "Take control back", + Size = new Vector2(180, 30), + Action = () => InputManager.UseParentInput = true }, } }, @@ -128,6 +122,13 @@ namespace osu.Game.Tests.Visual }); } + protected override void Update() + { + base.Update(); + + takeControlOverlay.Alpha = InputManager.UseParentInput ? 0 : 1; + } + /// /// Wait for a button to become enabled, then click it. /// @@ -146,19 +147,5 @@ namespace osu.Game.Tests.Visual InputManager.Click(MouseButton.Left); }); } - - protected override void Update() - { - base.Update(); - - buttonTest.Enabled.Value = InputManager.UseParentInput; - buttonLocal.Enabled.Value = !InputManager.UseParentInput; - } - - private void returnUserInput() => - InputManager.UseParentInput = true; - - private void returnTestInput() => - InputManager.UseParentInput = false; } } diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 1761282e2e..c82f642fdc 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -111,7 +111,7 @@ namespace osu.Game.Users protected string Username => score.User.Username; - public BeatmapInfo BeatmapInfo => score.BeatmapInfo; + public BeatmapInfo? BeatmapInfo => score.BeatmapInfo; public WatchingReplay(ScoreInfo score) { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 83d4bcb336..184a77a286 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,9 +35,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/osu.iOS.props b/osu.iOS.props index 86694e268a..51bcc36621 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -16,6 +16,6 @@ iossimulator-x64 - + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index b54794cd6d..482095db57 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -5,6 +5,7 @@ True ExplicitlyExcluded ExplicitlyExcluded + g_*.cs SOLUTION WARNING WARNING