diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 5d86b99fd7..a92191a439 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -1,5 +1,6 @@ M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead. +M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. diff --git a/Directory.Build.props b/Directory.Build.props index c8d441a78c..c0d740bac1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 3c237c86be..a6283eb7c4 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -34,12 +34,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty { mods = Score.Mods; - var legacyScore = Score as LegacyScoreInfo; - - fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect]; - ticksHit = legacyScore?.Count100 ?? 0; - tinyTicksHit = legacyScore?.Count50 ?? 0; - tinyTicksMissed = legacyScore?.CountKatu ?? 0; + fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect]; + ticksHit = Score?.GetCount100() ?? 0; + tinyTicksHit = Score?.GetCount50() ?? 0; + tinyTicksMissed = Score?.GetCountKatu() ?? 0; misses = Score.Statistics[HitResult.Miss]; // Don't count scores made with supposedly unranked mods diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index b99bddee96..3f7a2baedd 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -37,12 +37,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty { mods = Score.Mods; scaledScore = Score.TotalScore; - countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]); - countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); - countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); - countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]); - countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); - countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + countPerfect = Score.Statistics[HitResult.Perfect]; + countGreat = Score.Statistics[HitResult.Great]; + countGood = Score.Statistics[HitResult.Good]; + countOk = Score.Statistics[HitResult.Ok]; + countMeh = Score.Statistics[HitResult.Meh]; + countMiss = Score.Statistics[HitResult.Miss]; if (mods.Any(m => !m.Ranked)) return 0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 05c78cbc95..ce8ecf02ac 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -45,10 +45,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty mods = Score.Mods; accuracy = Score.Accuracy; scoreMaxCombo = Score.MaxCombo; - countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); - countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); - countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); - countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + countGreat = Score.Statistics[HitResult.Great]; + countGood = Score.Statistics[HitResult.Good]; + countMeh = Score.Statistics[HitResult.Meh]; + countMiss = Score.Statistics[HitResult.Miss]; // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index c3638253e4..3a0fb64622 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public override double Calculate(Dictionary categoryDifficulty = null) { mods = Score.Mods; - countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); - countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); - countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); - countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + countGreat = Score.Statistics[HitResult.Great]; + countGood = Score.Statistics[HitResult.Good]; + countMeh = Score.Statistics[HitResult.Meh]; + countMiss = Score.Statistics[HitResult.Miss]; // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) diff --git a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs new file mode 100644 index 0000000000..d1374eb6e5 --- /dev/null +++ b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Scoring; + +namespace osu.Game.Tests.Scores.IO +{ + [TestFixture] + public class TestScoreEquality + { + [Test] + public void TestNonMatchingByReference() + { + ScoreInfo score1 = new ScoreInfo(); + ScoreInfo score2 = new ScoreInfo(); + + Assert.That(score1, Is.Not.EqualTo(score2)); + } + + [Test] + public void TestMatchingByReference() + { + ScoreInfo score = new ScoreInfo(); + + Assert.That(score, Is.EqualTo(score)); + } + + [Test] + public void TestNonMatchingByPrimaryKey() + { + ScoreInfo score1 = new ScoreInfo { ID = 1 }; + ScoreInfo score2 = new ScoreInfo { ID = 2 }; + + Assert.That(score1, Is.Not.EqualTo(score2)); + } + + [Test] + public void TestMatchingByPrimaryKey() + { + ScoreInfo score1 = new ScoreInfo { ID = 1 }; + ScoreInfo score2 = new ScoreInfo { ID = 1 }; + + Assert.That(score1, Is.EqualTo(score2)); + } + + [Test] + public void TestNonMatchingByHash() + { + ScoreInfo score1 = new ScoreInfo { Hash = "a" }; + ScoreInfo score2 = new ScoreInfo { Hash = "b" }; + + Assert.That(score1, Is.Not.EqualTo(score2)); + } + + [Test] + public void TestMatchingByHash() + { + ScoreInfo score1 = new ScoreInfo { Hash = "a" }; + ScoreInfo score2 = new ScoreInfo { Hash = "a" }; + + Assert.That(score1, Is.EqualTo(score2)); + } + + [Test] + public void TestNonMatchingByNull() + { + ScoreInfo score = new ScoreInfo(); + + Assert.That(score, Is.Not.EqualTo(null)); + } + } +} diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f858174ff2..8f71584b4d 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -209,9 +209,10 @@ namespace osu.Game.Tests.Visual.Background public void TransitionTest() { performFullSetup(); - var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }); - AddStep("Transition to Results", () => player.Push(results)); - AddUntilStep("Wait for results is current", results.IsCurrentScreen); + FadeAccessibleResults results = null; + AddStep("Transition to Results", () => player.Push(results = + new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }))); + AddUntilStep("Wait for results is current", () => results.IsCurrentScreen()); waitForDim(); AddAssert("Screen is undimmed, original background retained", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 7b22fedbd5..8cb44de8cb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -5,11 +5,12 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Users; using System; using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Rulesets; using osu.Game.Screens.Ranking.Pages; namespace osu.Game.Tests.Visual.Gameplay @@ -17,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneReplayDownloadButton : OsuTestScene { + [Resolved] + private RulesetStore rulesets { get; set; } + public override IReadOnlyList RequiredTypes => new[] { typeof(ReplayDownloadButton) @@ -49,16 +53,15 @@ namespace osu.Game.Tests.Visual.Gameplay { return new APILegacyScoreInfo { - ID = 1, OnlineScoreID = 2553163309, - Ruleset = new OsuRuleset().RulesetInfo, + OnlineRulesetID = 0, Replay = replayAvailable, User = new User { Id = 39828, Username = @"WubWoofWolf", } - }; + }.CreateScoreInfo(rulesets); } private class TestReplayDownloadButton : ReplayDownloadButton diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index b26de1984a..b19f2dbf31 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -9,9 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; using osuTK.Graphics; @@ -66,12 +64,12 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"ES", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), + new OsuModDoubleTime().Acronym, + new OsuModHidden().Acronym, + new OsuModFlashlight().Acronym, + new OsuModHardRock().Acronym, }, Rank = ScoreRank.XH, PP = 200, @@ -91,11 +89,11 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"BR", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), + new OsuModDoubleTime().Acronym, + new OsuModHidden().Acronym, + new OsuModFlashlight().Acronym, }, Rank = ScoreRank.S, PP = 190, @@ -115,10 +113,10 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"JP", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), - new OsuModHidden(), + new OsuModDoubleTime().Acronym, + new OsuModHidden().Acronym, }, Rank = ScoreRank.B, PP = 180, @@ -138,9 +136,9 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"CA", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), + new OsuModDoubleTime().Acronym, }, Rank = ScoreRank.C, PP = 170, @@ -208,12 +206,12 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"ES", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), + new OsuModDoubleTime().Acronym, + new OsuModHidden().Acronym, + new OsuModFlashlight().Acronym, + new OsuModHardRock().Acronym, }, Rank = ScoreRank.XH, PP = 200, @@ -226,10 +224,13 @@ namespace osu.Game.Tests.Visual.Online foreach (var s in allScores.Scores) { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); + s.Statistics = new Dictionary + { + { "count_300", RNG.Next(2000) }, + { "count_100", RNG.Next(2000) }, + { "count_50", RNG.Next(2000) }, + { "count_miss", RNG.Next(2000) } + }; } AddStep("Load all scores", () => diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index fb27ec7654..57e297bcd5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; @@ -62,7 +61,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, User = new User { Id = 6602580, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 7fac45e0f1..e34e1844ce 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Shapes; using osuTK.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -52,7 +51,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, User = new User { Id = 6602580, diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 6456ce2d58..4d7abfe272 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -228,7 +228,7 @@ namespace osu.Game.Tournament if (b.BeatmapInfo == null && b.ID > 0) { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); - req.Perform(API); + API.Perform(req); b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); addedInfo = true; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index bc78e50f5d..a2e750cac5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -398,7 +398,7 @@ namespace osu.Game.Beatmaps try { // intentionally blocking to limit web request concurrency - req.Perform(api); + api.Perform(req); var res = req.Result; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 7351187ab9..39a0e6f6d4 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -25,6 +25,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// Whether equivalent. public abstract bool EquivalentTo(ControlPoint other); - public bool Equals(ControlPoint other) => Time.Equals(other?.Time) && EquivalentTo(other); + public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other); } } diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index 0b7d63f469..243060388f 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -99,17 +99,7 @@ namespace osu.Game.Database currentDownloads.Add(request); PostNotification?.Invoke(notification); - Task.Factory.StartNew(() => - { - try - { - request.Perform(api); - } - catch (Exception error) - { - triggerFailure(error); - } - }, TaskCreationOptions.LongRunning); + api.PerformAsync(request); DownloadBegan?.Invoke(request); return true; diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index b117d71006..facf70b47a 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -67,33 +67,21 @@ namespace osu.Game.Graphics.Containers // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceivePositionalInputAt(screenSpacePos); - protected override bool OnClick(ClickEvent e) - { - if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - Hide(); + private bool closeOnMouseUp; - return base.OnClick(e); + protected override bool OnMouseDown(MouseDownEvent e) + { + closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition); + + return base.OnMouseDown(e); } - private bool closeOnDragEnd; - - protected override bool OnDragStart(DragStartEvent e) + protected override bool OnMouseUp(MouseUpEvent e) { - if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - closeOnDragEnd = true; - - return base.OnDragStart(e); - } - - protected override bool OnDragEnd(DragEndEvent e) - { - if (closeOnDragEnd) - { + if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) Hide(); - closeOnDragEnd = false; - } - return base.OnDragEnd(e); + return base.OnMouseUp(e); } public virtual bool OnPressed(GlobalAction action) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 7412224f6c..563dc2dad9 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -151,18 +151,18 @@ namespace osu.Game.Graphics.UserInterface private void updateTooltipText(T value) { if (CurrentNumber.IsInteger) - TooltipText = ((int)Convert.ChangeType(value, typeof(int))).ToString("N0"); + TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); else { - double floatValue = (double)Convert.ChangeType(value, typeof(double)); - double floatMinValue = (double)Convert.ChangeType(CurrentNumber.MinValue, typeof(double)); - double floatMaxValue = (double)Convert.ChangeType(CurrentNumber.MaxValue, typeof(double)); + double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); + double floatMinValue = CurrentNumber.MinValue.ToDouble(NumberFormatInfo.InvariantInfo); + double floatMaxValue = CurrentNumber.MaxValue.ToDouble(NumberFormatInfo.InvariantInfo); if (floatMaxValue == 1 && floatMinValue >= -1) TooltipText = floatValue.ToString("P0"); else { - var decimalPrecision = normalise((decimal)Convert.ChangeType(CurrentNumber.Precision, typeof(decimal)), max_decimal_digits); + var decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); // Find the number of significant digits (we could have less than 5 after normalize()) var significantDigits = findPrecision(decimalPrecision); diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index e2b0e1b425..ff3618b263 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface public override bool OnPressed(PlatformAction action) { // Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox - // as we do not allow arrow key navigation in the first place (ie. the care should always be at the end of text) + // as we do not allow arrow key navigation in the first place (ie. the caret should always be at the end of text) // Avoid handling it here to allow other components to potentially consume the shortcut. if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) return false; diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index b6127027cc..1c45d26afd 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Net; using System.Net.Http; using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -198,6 +199,22 @@ namespace osu.Game.Online.API } } + public void Perform(APIRequest request) + { + try + { + request.Perform(this); + } + catch (Exception e) + { + // todo: fix exception handling + request.Fail(e); + } + } + + public Task PerformAsync(APIRequest request) => + Task.Factory.StartNew(() => Perform(request), TaskCreationOptions.LongRunning); + public void Login(string username, string password) { Debug.Assert(State == APIState.Offline); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 28132765d3..7f23f9b5d5 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Users; @@ -56,6 +57,10 @@ namespace osu.Game.Online.API { } + public void Perform(APIRequest request) { } + + public Task PerformAsync(APIRequest request) => Task.CompletedTask; + public void Register(IOnlineComponent component) { Scheduler.Add(delegate { components.Add(component); }); diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 0cd41aee26..dff6d0b2ce 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Game.Users; @@ -42,6 +43,24 @@ namespace osu.Game.Online.API /// The request to perform. void Queue(APIRequest request); + /// + /// Perform a request immediately, bypassing any API state checks. + /// + /// + /// Can be used to run requests as a guest user. + /// + /// The request to perform. + void Perform(APIRequest request); + + /// + /// Perform a request immediately, bypassing any API state checks. + /// + /// + /// Can be used to run requests as a guest user. + /// + /// The request to perform. + Task PerformAsync(APIRequest request); + /// /// Register a component to receive state changes. /// diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 50844fa256..bf3441d2a0 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using System.Text; using System.Collections.Generic; +using System.Diagnostics; namespace osu.Game.Online.API.Requests { @@ -37,10 +38,12 @@ namespace osu.Game.Online.API.Requests private void onSuccess(APILegacyScores r) { + Debug.Assert(ruleset.ID != null, "ruleset.ID != null"); + foreach (APILegacyScoreInfo score in r.Scores) { score.Beatmap = beatmap; - score.Ruleset = ruleset; + score.OnlineRulesetID = ruleset.ID.Value; } var userScore = r.UserScore; @@ -48,7 +51,7 @@ namespace osu.Game.Online.API.Requests if (userScore != null) { userScore.Score.Beatmap = beatmap; - userScore.Score.Ruleset = ruleset; + userScore.Score.OnlineRulesetID = ruleset.ID.Value; } } diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 17da255873..79ce04ed66 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -5,56 +5,106 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APILegacyScoreInfo : LegacyScoreInfo + public class APILegacyScoreInfo { - [JsonProperty(@"score")] - private int totalScore + public ScoreInfo CreateScoreInfo(RulesetStore rulesets) { - set => TotalScore = value; + var ruleset = rulesets.GetRuleset(OnlineRulesetID); + + var mods = Mods != null ? ruleset.CreateInstance().GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty(); + + var scoreInfo = new ScoreInfo + { + TotalScore = TotalScore, + MaxCombo = MaxCombo, + User = User, + Accuracy = Accuracy, + OnlineScoreID = OnlineScoreID, + Date = Date, + PP = PP, + Beatmap = Beatmap, + RulesetID = OnlineRulesetID, + Hash = "online", // todo: temporary? + Rank = Rank, + Ruleset = ruleset, + Mods = mods, + }; + + if (Statistics != null) + { + foreach (var kvp in Statistics) + { + switch (kvp.Key) + { + case @"count_geki": + scoreInfo.SetCountGeki(kvp.Value); + break; + + case @"count_300": + scoreInfo.SetCount300(kvp.Value); + break; + + case @"count_katu": + scoreInfo.SetCountKatu(kvp.Value); + break; + + case @"count_100": + scoreInfo.SetCount100(kvp.Value); + break; + + case @"count_50": + scoreInfo.SetCount50(kvp.Value); + break; + + case @"count_miss": + scoreInfo.SetCountMiss(kvp.Value); + break; + } + } + } + + return scoreInfo; } + [JsonProperty(@"score")] + public int TotalScore { get; set; } + [JsonProperty(@"max_combo")] - private int maxCombo - { - set => MaxCombo = value; - } + public int MaxCombo { get; set; } [JsonProperty(@"user")] - private User user - { - set => User = value; - } + public User User { get; set; } [JsonProperty(@"id")] - private long onlineScoreID - { - set => OnlineScoreID = value; - } + public long OnlineScoreID { get; set; } [JsonProperty(@"replay")] public bool Replay { get; set; } [JsonProperty(@"created_at")] - private DateTimeOffset date - { - set => Date = value; - } + public DateTimeOffset Date { get; set; } [JsonProperty(@"beatmap")] - private BeatmapInfo beatmap - { - set => Beatmap = value; - } + public BeatmapInfo Beatmap { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty(@"pp")] + public double? PP { get; set; } [JsonProperty(@"beatmapset")] - private BeatmapMetadata metadata + public BeatmapMetadata Metadata { set { @@ -67,68 +117,16 @@ namespace osu.Game.Online.API.Requests.Responses } [JsonProperty(@"statistics")] - private Dictionary jsonStats - { - set - { - foreach (var kvp in value) - { - switch (kvp.Key) - { - case @"count_geki": - CountGeki = kvp.Value; - break; - - case @"count_300": - Count300 = kvp.Value; - break; - - case @"count_katu": - CountKatu = kvp.Value; - break; - - case @"count_100": - Count100 = kvp.Value; - break; - - case @"count_50": - Count50 = kvp.Value; - break; - - case @"count_miss": - CountMiss = kvp.Value; - break; - - default: - continue; - } - } - } - } + public Dictionary Statistics { get; set; } [JsonProperty(@"mode_int")] - public int OnlineRulesetID - { - get => RulesetID; - set => RulesetID = value; - } + public int OnlineRulesetID { get; set; } [JsonProperty(@"mods")] - private string[] modStrings { get; set; } + public string[] Mods { get; set; } - public override RulesetInfo Ruleset - { - get => base.Ruleset; - set - { - base.Ruleset = value; - - if (modStrings != null) - { - // Evaluate the mod string - Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.Acronym)).ToArray(); - } - } - } + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; set; } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e978df404f..c7c746bed3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -406,11 +406,11 @@ namespace osu.Game nextBeatmap?.LoadBeatmapAsync(); } - private void currentTrackCompleted() + private void currentTrackCompleted() => Schedule(() => { if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) musicController.NextTrack(); - } + }); #endregion diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index f06f2ac802..0378d364b8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -41,6 +41,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + private GetScoresRequest getScoresRequest; protected APILegacyScores Scores @@ -56,16 +59,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; } - scoreTable.Scores = value.Scores; + var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); + + scoreTable.Scores = scoreInfos; scoreTable.Show(); - var topScore = value.Scores.First(); + var topScore = scoreInfos.First(); var userScore = value.UserScore; + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); topScoresContainer.Add(new DrawableTopScore(topScore)); - if (userScore != null && userScore.Score.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); + if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); }); } diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 3297b00322..67bcb6f558 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -43,18 +42,7 @@ namespace osu.Game.Overlays.Changelog }; req.Failure += _ => complete = true; - // This is done on a separate thread to support cancellation below - Task.Run(() => - { - try - { - req.Perform(api); - } - catch - { - complete = true; - } - }); + api.PerformAsync(req); while (!complete) { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 559989af5c..fbc9dfcbd9 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -191,15 +191,7 @@ namespace osu.Game.Overlays tcs.SetResult(false); }; - try - { - req.Perform(API); - } - catch - { - initialFetchTask = null; - tcs.SetResult(false); - } + await API.PerformAsync(req); await tcs.Task; }); diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index f1ae5d64f5..a9b4bed334 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -16,6 +16,7 @@ using osu.Game.Users; namespace osu.Game.Overlays.MedalSplash { + [LongRunningLoad] public class DrawableMedal : Container, IStateful { private const float scale_when_unlocked = 0.76f; diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs index ea259fe49a..7eed4d3b6b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -12,6 +12,7 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { + [LongRunningLoad] public class DrawableBadge : CompositeDrawable, IHasTooltip { public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index 23fe6e9cd5..03ee29d0c2 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -19,8 +19,8 @@ namespace osu.Game.Overlays.Profile.Sections private const int fade_duration = 200; private Box underscoreLine; - private readonly Box coloredBackground; - private readonly Container background; + private Box coloredBackground; + private Container background; /// /// A visual element displayed to the left of content. @@ -36,6 +36,19 @@ namespace osu.Game.Overlays.Profile.Sections { RelativeSizeAxes = Axes.X; Height = 60; + + Content = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 0.97f, + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colour) + { InternalChildren = new Drawable[] { background = new Container @@ -53,21 +66,7 @@ namespace osu.Game.Overlays.Profile.Sections }, Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both } }, - Content = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 0.97f, - }, - }; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colour) - { - AddRange(new Drawable[] - { + Content, underscoreLine = new Box { Anchor = Anchor.BottomCentre, @@ -101,7 +100,7 @@ namespace osu.Game.Overlays.Profile.Sections Origin = Anchor.CentreRight, Direction = FillDirection.Vertical, }, - }); + }; coloredBackground.Colour = underscoreLine.Colour = colour.Gray4; } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 853b9db0a7..5b58fc0930 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -29,14 +29,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks ItemsContainer.Direction = FillDirection.Vertical; } - protected override void UpdateItems(List items) - { - foreach (var item in items) - item.Ruleset = Rulesets.GetRuleset(item.RulesetID); - - base.UpdateItems(items); - } - protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); @@ -45,10 +37,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks switch (type) { default: - return new DrawablePerformanceScore(model, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); + return new DrawablePerformanceScore(model.CreateScoreInfo(Rulesets), includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); case ScoreType.Recent: - return new DrawableTotalScore(model); + return new DrawableTotalScore(model.CreateScoreInfo(Rulesets)); } } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index b5a508bff7..4e856845ac 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -66,11 +66,14 @@ namespace osu.Game.Overlays.Profile.Sections.Recent }; case RecentActivityType.Achievement: - return new MedalIcon(activity.Achievement.Slug) + return new DelayedLoadWrapper(new MedalIcon(activity.Achievement.Slug) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + }) { RelativeSizeAxes = Axes.Y, Width = 60, - FillMode = FillMode.Fit, }; default: diff --git a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs index 56ff4d4dec..4563510046 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Overlays.Profile.Sections.Recent { + [LongRunningLoad] public class MedalIcon : Container { private readonly string slug; diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 8161baba70..ae6aad5b9c 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -273,14 +273,6 @@ namespace osu.Game.Rulesets.Objects return p0 + (p1 - p0) * (float)w; } - public bool Equals(SliderPath other) - { - if (ControlPoints == null && other.ControlPoints != null) - return false; - if (other.ControlPoints == null && ControlPoints != null) - return false; - - return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance.Equals(other.ExpectedDistance) && Type == other.Type; - } + public bool Equals(SliderPath other) => ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance == other.ExpectedDistance && Type == other.Type; } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs deleted file mode 100644 index e66f93ec8d..0000000000 --- a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Scoring.Legacy -{ - public class LegacyScoreInfo : ScoreInfo - { - private int countGeki; - - public int CountGeki - { - get => countGeki; - set - { - countGeki = value; - - switch (Ruleset?.ID ?? RulesetID) - { - case 3: - Statistics[HitResult.Perfect] = value; - break; - } - } - } - - private int count300; - - public int Count300 - { - get => count300; - set - { - count300 = value; - - switch (Ruleset?.ID ?? RulesetID) - { - case 0: - case 1: - case 3: - Statistics[HitResult.Great] = value; - break; - - case 2: - Statistics[HitResult.Perfect] = value; - break; - } - } - } - - private int countKatu; - - public int CountKatu - { - get => countKatu; - set - { - countKatu = value; - - switch (Ruleset?.ID ?? RulesetID) - { - case 3: - Statistics[HitResult.Good] = value; - break; - } - } - } - - private int count100; - - public int Count100 - { - get => count100; - set - { - count100 = value; - - switch (Ruleset?.ID ?? RulesetID) - { - case 0: - case 1: - Statistics[HitResult.Good] = value; - break; - - case 3: - Statistics[HitResult.Ok] = value; - break; - } - } - } - - private int count50; - - public int Count50 - { - get => count50; - set - { - count50 = value; - - switch (Ruleset?.ID ?? RulesetID) - { - case 0: - case 3: - Statistics[HitResult.Meh] = value; - break; - } - } - } - - public int CountMiss - { - get => Statistics[HitResult.Miss]; - set => Statistics[HitResult.Miss] = value; - } - } -} diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 2cdd0c4b5e..0029c843b4 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -35,7 +35,7 @@ namespace osu.Game.Scoring.Legacy using (SerializationReader sr = new SerializationReader(stream)) { currentRuleset = GetRuleset(sr.ReadByte()); - var scoreInfo = new LegacyScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + var scoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo }; score.ScoreInfo = scoreInfo; @@ -53,12 +53,12 @@ namespace osu.Game.Scoring.Legacy // MD5Hash sr.ReadString(); - scoreInfo.Count300 = sr.ReadUInt16(); - scoreInfo.Count100 = sr.ReadUInt16(); - scoreInfo.Count50 = sr.ReadUInt16(); - scoreInfo.CountGeki = sr.ReadUInt16(); - scoreInfo.CountKatu = sr.ReadUInt16(); - scoreInfo.CountMiss = sr.ReadUInt16(); + scoreInfo.SetCount300(sr.ReadUInt16()); + scoreInfo.SetCount100(sr.ReadUInt16()); + scoreInfo.SetCount50(sr.ReadUInt16()); + scoreInfo.SetCountGeki(sr.ReadUInt16()); + scoreInfo.SetCountKatu(sr.ReadUInt16()); + scoreInfo.SetCountMiss(sr.ReadUInt16()); scoreInfo.TotalScore = sr.ReadInt32(); scoreInfo.MaxCombo = sr.ReadUInt16(); diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs new file mode 100644 index 0000000000..66b1acf591 --- /dev/null +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -0,0 +1,143 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring.Legacy +{ + public static class ScoreInfoExtensions + { + public static int? GetCountGeki(this ScoreInfo scoreInfo) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 3: + return scoreInfo.Statistics[HitResult.Perfect]; + } + + return null; + } + + public static void SetCountGeki(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 3: + scoreInfo.Statistics[HitResult.Perfect] = value; + break; + } + } + + public static int? GetCount300(this ScoreInfo scoreInfo) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 1: + case 3: + return scoreInfo.Statistics[HitResult.Great]; + + case 2: + return scoreInfo.Statistics[HitResult.Perfect]; + } + + return null; + } + + public static void SetCount300(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 1: + case 3: + scoreInfo.Statistics[HitResult.Great] = value; + break; + + case 2: + scoreInfo.Statistics[HitResult.Perfect] = value; + break; + } + } + + public static int? GetCountKatu(this ScoreInfo scoreInfo) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 3: + return scoreInfo.Statistics[HitResult.Good]; + } + + return null; + } + + public static void SetCountKatu(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 3: + scoreInfo.Statistics[HitResult.Good] = value; + break; + } + } + + public static int? GetCount100(this ScoreInfo scoreInfo) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 1: + return scoreInfo.Statistics[HitResult.Good]; + + case 3: + return scoreInfo.Statistics[HitResult.Ok]; + } + + return null; + } + + public static void SetCount100(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 1: + scoreInfo.Statistics[HitResult.Good] = value; + break; + + case 3: + scoreInfo.Statistics[HitResult.Ok] = value; + break; + } + } + + public static int? GetCount50(this ScoreInfo scoreInfo) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 3: + return scoreInfo.Statistics[HitResult.Meh]; + } + + return null; + } + + public static void SetCount50(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 3: + scoreInfo.Statistics[HitResult.Meh] = value; + break; + } + } + + public static int? GetCountMiss(this ScoreInfo scoreInfo) => + scoreInfo.Statistics[HitResult.Miss]; + + public static void SetCountMiss(this ScoreInfo scoreInfo, int value) => + scoreInfo.Statistics[HitResult.Miss] = value; + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index f7bac82e74..c7609e8a0b 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -183,10 +183,21 @@ namespace osu.Game.Scoring public override string ToString() => $"{User} playing {Beatmap}"; - public bool Equals(ScoreInfo other) => - other != null - && other.OnlineScoreID == OnlineScoreID - && other.BeatmapInfoID == BeatmapInfoID - && other.Hash == Hash; + public bool Equals(ScoreInfo other) + { + if (other == null) + return false; + + if (ID != 0 && other.ID != 0) + return ID == other.ID; + + if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue) + return OnlineScoreID == other.OnlineScoreID; + + if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) + return Hash == other.Hash; + + return ReferenceEquals(this, other); + } } } diff --git a/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs index 73c647d6fa..9cc6ea2628 100644 --- a/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osuTK; @@ -24,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Pages if (State.Value == DownloadState.LocallyAvailable) return ReplayAvailability.Local; - if (Model.Value is APILegacyScoreInfo apiScore && apiScore.Replay) + if (!string.IsNullOrEmpty(Model.Value.Hash)) return ReplayAvailability.Online; return ReplayAvailability.NotAvailable; diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 27cea99f1c..43234c0b29 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -70,7 +70,10 @@ namespace osu.Game.Screens.Ranking.Pages Direction = FillDirection.Vertical, Children = new Drawable[] { - new UserHeader(Score.User) + new DelayedLoadWrapper(new UserHeader(Score.User) + { + RelativeSizeAxes = Axes.Both, + }) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -370,6 +373,7 @@ namespace osu.Game.Screens.Ranking.Pages } } + [LongRunningLoad] private class UserHeader : Container { private readonly User user; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c2cbac905e..7971506fa3 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -79,8 +79,8 @@ namespace osu.Game.Screens.Select public bool IsUpperInclusive; public bool Equals(OptionalRange other) - => Min.Equals(other.Min) - && Max.Equals(other.Max) + => EqualityComparer.Default.Equals(Min, other.Min) + && EqualityComparer.Default.Equals(Max, other.Max) && IsLowerInclusive.Equals(other.IsLowerInclusive) && IsUpperInclusive.Equals(other.IsUpperInclusive); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3ef1fe5bc5..1b45a9d270 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -21,6 +21,9 @@ namespace osu.Game.Screens.Select.Leaderboards { public Action ScoreSelected; + [Resolved] + private RulesetStore rulesets { get; set; } + private BeatmapInfo beatmap; public BeatmapInfo Beatmap @@ -172,7 +175,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoresCallback?.Invoke(r.Scores); + scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); TopScore = r.UserScore; }; diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs index a787eb5629..8e10734454 100644 --- a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs @@ -3,6 +3,7 @@ using System; using System.Threading; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -10,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; using osu.Game.Scoring; using osuTK; @@ -27,6 +29,9 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool StartHidden => true; + [Resolved] + private RulesetStore rulesets { get; set; } + public UserTopScoreContainer() { RelativeSizeAxes = Axes.X; @@ -77,9 +82,11 @@ namespace osu.Game.Screens.Select.Leaderboards if (newScore == null) return; - LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position, false) + var scoreInfo = newScore.Score.CreateScoreInfo(rulesets); + + LoadComponentAsync(new LeaderboardScore(scoreInfo, newScore.Position, false) { - Action = () => ScoreSelected?.Invoke(newScore.Score) + Action = () => ScoreSelected?.Invoke(scoreInfo) }, drawableScore => { scoreContainer.Child = drawableScore; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index b144de35c5..ef86186e41 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -240,6 +240,6 @@ namespace osu.Game.Tests.Beatmaps set => Objects = value; } - public virtual bool Equals(ConvertMapping other) => StartTime.Equals(other?.StartTime); + public virtual bool Equals(ConvertMapping other) => StartTime == other?.StartTime; } }