From b31fef7e001c22f5e55c01ff8c3fa4df9c090b01 Mon Sep 17 00:00:00 2001 From: 02Naitsirk <57512128+02Naitsirk@users.noreply.github.com> Date: Thu, 22 Jul 2021 13:49:47 -0400 Subject: [PATCH 001/207] Implement total SR formula that better correlates with pp --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index e47f82fb39..2f0b7da789 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -34,7 +34,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; + + double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating) - 4, 3) / 100000; + double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating) - 4, 3) / 100000; + double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1); + double starRating = basePerformance > 0.00001 ? 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; HitWindows hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); From 5b5cf30cbd5b862496a26453b6edb5ee69281d1c Mon Sep 17 00:00:00 2001 From: 02Naitsirk <57512128+02Naitsirk@users.noreply.github.com> Date: Sat, 31 Jul 2021 12:23:03 -0400 Subject: [PATCH 002/207] Fix incorrect performance formula MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 2f0b7da789..299d8b8970 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating) - 4, 3) / 100000; - double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating) - 4, 3) / 100000; + double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; + double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1); double starRating = basePerformance > 0.00001 ? 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; From ea2ef55a8bcc2867ae414e84c67857263cac8be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Aug 2021 15:27:05 +0200 Subject: [PATCH 003/207] Remove unnecessary whitespace --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 299d8b8970..b75bf81071 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - + double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1); From c371e67d705df820ded3c66344a27cf3d1b07b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Aug 2021 15:32:10 +0200 Subject: [PATCH 004/207] Update diffcalc test to match ground-truth formula --- .../OsuDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 8d8387378e..32fba0b70c 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.7568168283591499d, "diffcalc-test")] - [TestCase(1.0348244046058293d, "zero-length-sliders")] + [TestCase(6.4164199087433484d, "diffcalc-test")] + [TestCase(1.0028132594779837d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.4783236764532557d, "diffcalc-test")] - [TestCase(1.2708532136987165d, "zero-length-sliders")] + [TestCase(8.0749334911818469d, "diffcalc-test")] + [TestCase(1.2251606765323657d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From db1f43f6eb887c2d4c38ad796f00dd075821fb25 Mon Sep 17 00:00:00 2001 From: 02Naitsirk <57512128+02Naitsirk@users.noreply.github.com> Date: Tue, 3 Aug 2021 18:57:33 -0400 Subject: [PATCH 005/207] Multiply star rating by a constant --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index b75bf81071..da879cb02e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1); - double starRating = basePerformance > 0.00001 ? 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; + double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; HitWindows hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); From 0d2ab550dd8c1285d94ae8991e040f06ea9a970a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Aug 2021 13:04:32 +0900 Subject: [PATCH 006/207] Update SR values in tests --- .../OsuDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 32fba0b70c..19881b5c33 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.4164199087433484d, "diffcalc-test")] - [TestCase(1.0028132594779837d, "zero-length-sliders")] + [TestCase(6.6634445062299665d, "diffcalc-test")] + [TestCase(1.0414203870195022d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.0749334911818469d, "diffcalc-test")] - [TestCase(1.2251606765323657d, "zero-length-sliders")] + [TestCase(8.3858089051603368d, "diffcalc-test")] + [TestCase(1.2723279173428435d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From 47061c0210ad37b80ba5778bf205ca91dcc0b132 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 18:57:30 +0900 Subject: [PATCH 007/207] Trigger refresh on scoring mode change --- .../BeatmapSet/Scores/ScoresContainer.cs | 71 ++++++++++++------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index aff48919b4..bfdb90c36a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -12,8 +12,11 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Bindables; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -27,6 +30,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Bindable ruleset = new Bindable(); private readonly Bindable scope = new Bindable(BeatmapLeaderboardScope.Global); private readonly IBindable user = new Bindable(); + private readonly Bindable scoringMode = new Bindable(); private readonly Box background; private readonly ScoreTable scoreTable; @@ -42,35 +46,20 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private ScoreManager scoreManager { get; set; } + private GetScoresRequest getScoresRequest; + private APILegacyScores scores; + protected APILegacyScores Scores { - set => Schedule(() => + set { - topScoresContainer.Clear(); - - if (value?.Scores.Any() != true) - { - scoreTable.ClearScores(); - scoreTable.Hide(); - return; - } - - var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); - var topScore = scoreInfos.First(); - - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); - scoreTable.Show(); - - var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); - - topScoresContainer.Add(new DrawableTopScore(topScore)); - - if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); - }); + scores = value; + displayScores(value); + } } public ScoresContainer() @@ -166,11 +155,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuConfigManager config) { background.Colour = colourProvider.Background5; user.BindTo(api.LocalUser); + config.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); } protected override void LoadComplete() @@ -183,6 +173,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Beatmap.BindValueChanged(onBeatmapChanged); user.BindValueChanged(onUserChanged, true); + + scoringMode.BindValueChanged(_ => displayScores(scores)); } private void onBeatmapChanged(ValueChangedEvent beatmap) @@ -254,6 +246,35 @@ namespace osu.Game.Overlays.BeatmapSet.Scores api.Queue(getScoresRequest); } + private void displayScores(APILegacyScores newScores) + { + Schedule(() => + { + topScoresContainer.Clear(); + + if (newScores?.Scores.Any() != true) + { + scoreTable.ClearScores(); + scoreTable.Hide(); + return; + } + + var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); + var topScore = scoreInfos.First(); + + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); + scoreTable.Show(); + + var userScore = newScores.UserScore; + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); + + topScoresContainer.Add(new DrawableTopScore(topScore)); + + if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); + }); + } + private bool userIsSupporter => api.IsLoggedIn && api.LocalUser.Value.IsSupporter; } } From b217dd1a658b590625a913f361e596ea8587a82c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:03:16 +0900 Subject: [PATCH 008/207] Order scores by score --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index bfdb90c36a..1e5f7c3f72 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -259,7 +259,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; } - var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); + var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)) + .OrderByDescending(s => scoreManager.GetBindableTotalScore(s).Value) + .ToList(); + var topScore = scoreInfos.First(); scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); From d03950fb370869e18f2e9050b2148a06b0393084 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:33:09 +0900 Subject: [PATCH 009/207] Move score calculation to ScoreManager --- osu.Game/Scoring/ScoreManager.cs | 144 ++++++++++++++----------------- 1 file changed, 67 insertions(+), 77 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 83bcac01ac..f310462d6e 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -34,6 +34,7 @@ namespace osu.Game.Scoring protected override string ImportFromStablePath => Path.Combine("Data", "r"); + private readonly Bindable scoringMode = new Bindable(); private readonly RulesetStore rulesets; private readonly Func beatmaps; @@ -51,6 +52,8 @@ namespace osu.Game.Scoring this.beatmaps = beatmaps; this.difficulties = difficulties; this.configManager = configManager; + + configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -113,7 +116,7 @@ namespace osu.Game.Scoring /// The bindable containing the total score. public Bindable GetBindableTotalScore(ScoreInfo score) { - var bindable = new TotalScoreBindable(score, difficulties); + var bindable = new TotalScoreBindable(score, this); configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); return bindable; } @@ -128,6 +131,63 @@ namespace osu.Game.Scoring /// The bindable containing the formatted total score string. public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); + public long GetTotalScore(ScoreInfo score) => GetTotalScoreAsync(score).Result; + + public async Task GetTotalScoreAsync(ScoreInfo score, CancellationToken cancellationToken = default) + { + if (score.Beatmap == null) + return score.TotalScore; + + int beatmapMaxCombo; + double accuracy = score.Accuracy; + + if (score.IsLegacyScore) + { + if (score.RulesetID == 3) + { + // In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score. + // To get around this, recalculate accuracy based on the hit statistics. + // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. + double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect); + double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum(); + if (maxBaseScore > 0) + accuracy = baseScore / maxBaseScore; + } + + // This score is guaranteed to be an osu!stable score. + // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. + if (score.Beatmap.MaxCombo != null) + beatmapMaxCombo = score.Beatmap.MaxCombo.Value; + else + { + if (score.Beatmap.ID == 0 || difficulties == null) + { + // We don't have enough information (max combo) to compute the score, so use the provided score. + return score.TotalScore; + } + + // We can compute the max combo locally after the async beatmap difficulty computation. + var difficulty = await difficulties().GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); + beatmapMaxCombo = difficulty.MaxCombo; + } + } + else + { + // This is guaranteed to be a non-legacy score. + // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. + beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum(); + } + + if (beatmapMaxCombo == 0) + return 0; + + var ruleset = score.Ruleset.CreateInstance(); + var scoreProcessor = ruleset.CreateScoreProcessor(); + scoreProcessor.Mods.Value = score.Mods; + + return (long)Math.Round(scoreProcessor.GetScore(scoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + } + /// /// Provides the total score of a . Responds to changes in the currently-selected . /// @@ -136,99 +196,29 @@ namespace osu.Game.Scoring public readonly Bindable ScoringMode = new Bindable(); private readonly ScoreInfo score; - private readonly Func difficulties; + private readonly ScoreManager scoreManager; /// /// Creates a new . /// /// The to provide the total score of. - /// A function to retrieve the . - public TotalScoreBindable(ScoreInfo score, Func difficulties) + /// The . + public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) { this.score = score; - this.difficulties = difficulties; + this.scoreManager = scoreManager; ScoringMode.BindValueChanged(onScoringModeChanged, true); } - private IBindable difficultyBindable; private CancellationTokenSource difficultyCancellationSource; private void onScoringModeChanged(ValueChangedEvent mode) { difficultyCancellationSource?.Cancel(); - difficultyCancellationSource = null; + difficultyCancellationSource = new CancellationTokenSource(); - if (score.Beatmap == null) - { - Value = score.TotalScore; - return; - } - - int beatmapMaxCombo; - double accuracy = score.Accuracy; - - if (score.IsLegacyScore) - { - if (score.RulesetID == 3) - { - // In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score. - // To get around this, recalculate accuracy based on the hit statistics. - // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. - double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect); - double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum(); - if (maxBaseScore > 0) - accuracy = baseScore / maxBaseScore; - } - - // This score is guaranteed to be an osu!stable score. - // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. - if (score.Beatmap.MaxCombo == null) - { - if (score.Beatmap.ID == 0 || difficulties == null) - { - // We don't have enough information (max combo) to compute the score, so use the provided score. - Value = score.TotalScore; - return; - } - - // We can compute the max combo locally after the async beatmap difficulty computation. - difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); - difficultyBindable.BindValueChanged(d => - { - if (d.NewValue is StarDifficulty diff) - updateScore(diff.MaxCombo, accuracy); - }, true); - - return; - } - - beatmapMaxCombo = score.Beatmap.MaxCombo.Value; - } - else - { - // This is guaranteed to be a non-legacy score. - // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. - beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum(); - } - - updateScore(beatmapMaxCombo, accuracy); - } - - private void updateScore(int beatmapMaxCombo, double accuracy) - { - if (beatmapMaxCombo == 0) - { - Value = 0; - return; - } - - var ruleset = score.Ruleset.CreateInstance(); - var scoreProcessor = ruleset.CreateScoreProcessor(); - - scoreProcessor.Mods.Value = score.Mods; - - Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + scoreManager.GetTotalScoreAsync(score, difficultyCancellationSource.Token).ContinueWith(s => Value = s.Result, TaskContinuationOptions.OnlyOnRanToCompletion); } } From 458ce250f01c7d81f05c78d2382d1151726cc82a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:34:12 +0900 Subject: [PATCH 010/207] Use new ScoreManager method in ScoreTable --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index a154016824..847df53edc 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -151,8 +151,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Current = scoreManager.GetBindableTotalScoreString(score), - Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) + Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium), + Text = scoreManager.GetTotalScore(score).ToLocalisableString(@"N0"), }, new OsuSpriteText { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 1e5f7c3f72..a0ccb5a393 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -260,7 +260,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)) - .OrderByDescending(s => scoreManager.GetBindableTotalScore(s).Value) + .OrderByDescending(s => scoreManager.GetTotalScore(s)) .ToList(); var topScore = scoreInfos.First(); From 4ebb11472df20b65efc4e0f13cfb4bac6f7a6878 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:34:34 +0900 Subject: [PATCH 011/207] Update Leaderboard to reorder scores based on scoring mode --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 +++++++- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 4f8b27602b..f3a06994ee 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -13,11 +13,13 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Threading; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Placeholders; +using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -27,6 +29,7 @@ namespace osu.Game.Online.Leaderboards { private const double fade_duration = 300; + private readonly Bindable scoringMode = new Bindable(); private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; private readonly UserTopScoreContainer topScoreContainer; @@ -246,12 +249,15 @@ namespace osu.Game.Online.Leaderboards private readonly IBindable apiState = new Bindable(); [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager configManager) { if (api != null) apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); + + configManager.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); + scoringMode.BindValueChanged(_ => RefreshScores(), true); } private APIRequest getScoresRequest; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 934b905a1a..206b0e7936 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -198,8 +198,8 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.Numeric.With(size: 23), + Text = scoreManager.GetTotalScore(score).ToLocalisableString(@"N0"), }, RankContainer = new Container { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index a86a614a05..04e7172519 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -146,7 +146,7 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - Scores = scores.OrderByDescending(s => s.TotalScore).ToArray(); + Scores = scores.OrderByDescending(s => scoreManager.GetTotalScore(s)).ToArray(); PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return null; @@ -182,7 +182,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); + scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).OrderByDescending(s => scoreManager.GetTotalScore(s))); TopScore = r.UserScore?.CreateScoreInfo(rulesets); }; From e19d81c88ce21aba8cf19219142a60a9ff356236 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:41:44 +0900 Subject: [PATCH 012/207] Fix potential incorrect ordering --- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 1 + .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index a0ccb5a393..91c3dfad65 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -261,6 +261,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)) .OrderByDescending(s => scoreManager.GetTotalScore(s)) + .ThenBy(s => s.OnlineScoreID) .ToList(); var topScore = scoreInfos.First(); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 04e7172519..0276ae8b2a 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -146,7 +146,10 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - Scores = scores.OrderByDescending(s => scoreManager.GetTotalScore(s)).ToArray(); + Scores = scores.OrderByDescending(s => scoreManager.GetTotalScore(s)) + .ThenBy(s => s.OnlineScoreID) + .ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return null; @@ -182,7 +185,11 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).OrderByDescending(s => scoreManager.GetTotalScore(s))); + scoresCallback?.Invoke( + r.Scores.Select(s => s.CreateScoreInfo(rulesets)) + .OrderByDescending(s => scoreManager.GetTotalScore(s)) + .ThenBy(s => s.OnlineScoreID)); + TopScore = r.UserScore?.CreateScoreInfo(rulesets); }; From caa797cbf46ad46d45832a14b709a8b9641fea6f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:58:35 +0900 Subject: [PATCH 013/207] Attempt to reorder score panel list --- osu.Game/Screens/Ranking/ScorePanelList.cs | 48 +++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e170241ede..c9ddfd27d3 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -5,11 +5,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osuTK; using osuTK.Input; @@ -289,27 +292,42 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); + private readonly Bindable scoringMode = new Bindable(); + + [Resolved] + private ScoreManager scoreManager { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager configManager) + { + configManager.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + scoringMode.BindValueChanged(mode => + { + foreach (var c in Children) + SetLayoutPosition(c, scoreManager.GetTotalScore(c.Panel.Score)); + }, true); + } + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() - .OrderByDescending(s => s.Panel.Score.TotalScore) - .ThenBy(s => s.Panel.Score.OnlineScoreID); - - protected override int Compare(Drawable x, Drawable y) + public override void Add(ScorePanelTrackingContainer drawable) { - var tX = (ScorePanelTrackingContainer)x; - var tY = (ScorePanelTrackingContainer)y; + Debug.Assert(drawable != null); - int result = tY.Panel.Score.TotalScore.CompareTo(tX.Panel.Score.TotalScore); + base.Add(drawable); - if (result != 0) - return result; - - if (tX.Panel.Score.OnlineScoreID == null || tY.Panel.Score.OnlineScoreID == null) - return base.Compare(x, y); - - return tX.Panel.Score.OnlineScoreID.Value.CompareTo(tY.Panel.Score.OnlineScoreID.Value); + SetLayoutPosition(drawable, scoreManager?.GetTotalScore(drawable.Panel.Score) ?? 0); } + + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(GetLayoutPosition) + .ThenBy(s => s.Panel.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer From 90768a86a65db383aa2de2d58756c2a59bc80139 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 17:55:13 +0900 Subject: [PATCH 014/207] Adjust classic scoring as a constant multiple over standardised scoring --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 16f2607bad..3073ec9340 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -226,8 +226,9 @@ namespace osu.Game.Rulesets.Scoring return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: - // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return getBonusScore(statistics) + (accuracyRatio * Math.Max(1, maxCombo) * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); + // This feels very similar to osu!stable scoring (ScoreV1) while maintaining that classic scoring is only a constant multiple of standardised scoring. + // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two systems. + return GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score * Math.Pow(maxCombo, 2) * 25; } } From bfcadcc4ac591b8128e26ff80083c478075e367f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 18:56:26 +0900 Subject: [PATCH 015/207] Revert some changes --- osu.Game/Online/Leaderboards/Leaderboard.cs | 5 -- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 72 ++++++++----------- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 +---- 5 files changed, 32 insertions(+), 66 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index f3a06994ee..1d9d2e6b14 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Placeholders; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -29,7 +28,6 @@ namespace osu.Game.Online.Leaderboards { private const double fade_duration = 300; - private readonly Bindable scoringMode = new Bindable(); private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; private readonly UserTopScoreContainer topScoreContainer; @@ -255,9 +253,6 @@ namespace osu.Game.Online.Leaderboards apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); - - configManager.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); - scoringMode.BindValueChanged(_ => RefreshScores(), true); } private APIRequest getScoresRequest; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 206b0e7936..9a0bc35bb3 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -199,7 +199,7 @@ namespace osu.Game.Online.Leaderboards TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), Font = OsuFont.Numeric.With(size: 23), - Text = scoreManager.GetTotalScore(score).ToLocalisableString(@"N0"), + Current = scoreManager.GetBindableTotalScoreString(score) }, RankContainer = new Container { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 847df53edc..9497ed9d35 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -152,7 +152,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Margin = new MarginPadding { Right = horizontal_inset }, Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium), - Text = scoreManager.GetTotalScore(score).ToLocalisableString(@"N0"), + Current = scoreManager.GetBindableTotalScoreString(score), }, new OsuSpriteText { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 91c3dfad65..1b7e152c07 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -15,7 +15,6 @@ using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -30,7 +29,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Bindable ruleset = new Bindable(); private readonly Bindable scope = new Bindable(BeatmapLeaderboardScope.Global); private readonly IBindable user = new Bindable(); - private readonly Bindable scoringMode = new Bindable(); private readonly Box background; private readonly ScoreTable scoreTable; @@ -51,15 +49,37 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private GetScoresRequest getScoresRequest; - private APILegacyScores scores; - protected APILegacyScores Scores { - set + set => Schedule(() => { - scores = value; - displayScores(value); - } + topScoresContainer.Clear(); + + if (value?.Scores.Any() != true) + { + scoreTable.ClearScores(); + scoreTable.Hide(); + return; + } + + var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)) + .OrderByDescending(s => scoreManager.GetTotalScore(s)) + .ThenBy(s => s.OnlineScoreID) + .ToList(); + + var topScore = scoreInfos.First(); + + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); + scoreTable.Show(); + + var userScore = value.UserScore; + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); + + topScoresContainer.Add(new DrawableTopScore(topScore)); + + if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); + }); } public ScoresContainer() @@ -160,7 +180,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores background.Colour = colourProvider.Background5; user.BindTo(api.LocalUser); - config.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); } protected override void LoadComplete() @@ -173,8 +192,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Beatmap.BindValueChanged(onBeatmapChanged); user.BindValueChanged(onUserChanged, true); - - scoringMode.BindValueChanged(_ => displayScores(scores)); } private void onBeatmapChanged(ValueChangedEvent beatmap) @@ -246,39 +263,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores api.Queue(getScoresRequest); } - private void displayScores(APILegacyScores newScores) - { - Schedule(() => - { - topScoresContainer.Clear(); - - if (newScores?.Scores.Any() != true) - { - scoreTable.ClearScores(); - scoreTable.Hide(); - return; - } - - var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)) - .OrderByDescending(s => scoreManager.GetTotalScore(s)) - .ThenBy(s => s.OnlineScoreID) - .ToList(); - - var topScore = scoreInfos.First(); - - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); - scoreTable.Show(); - - var userScore = newScores.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); - - topScoresContainer.Add(new DrawableTopScore(topScore)); - - if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); - }); - } - private bool userIsSupporter => api.IsLoggedIn && api.LocalUser.Value.IsSupporter; } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index c9ddfd27d3..ba90585ab5 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -10,9 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Configuration; using osu.Game.Graphics.Containers; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osuTK; using osuTK.Input; @@ -292,26 +290,15 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - private readonly Bindable scoringMode = new Bindable(); - [Resolved] private ScoreManager scoreManager { get; set; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager configManager) - { - configManager.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); - } - protected override void LoadComplete() { base.LoadComplete(); - scoringMode.BindValueChanged(mode => - { - foreach (var c in Children) - SetLayoutPosition(c, scoreManager.GetTotalScore(c.Panel.Score)); - }, true); + foreach (var c in Children) + SetLayoutPosition(c, scoreManager.GetTotalScore(c.Panel.Score)); } public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); From cfcf3d7507b8b80b89d082e3027d354bcffa5220 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 20:43:50 +0900 Subject: [PATCH 016/207] Use synchronous total score retrieval for bindable --- osu.Game/Scoring/ScoreManager.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index f310462d6e..8cf1c85956 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -195,9 +195,6 @@ namespace osu.Game.Scoring { public readonly Bindable ScoringMode = new Bindable(); - private readonly ScoreInfo score; - private readonly ScoreManager scoreManager; - /// /// Creates a new . /// @@ -205,20 +202,7 @@ namespace osu.Game.Scoring /// The . public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) { - this.score = score; - this.scoreManager = scoreManager; - - ScoringMode.BindValueChanged(onScoringModeChanged, true); - } - - private CancellationTokenSource difficultyCancellationSource; - - private void onScoringModeChanged(ValueChangedEvent mode) - { - difficultyCancellationSource?.Cancel(); - difficultyCancellationSource = new CancellationTokenSource(); - - scoreManager.GetTotalScoreAsync(score, difficultyCancellationSource.Token).ContinueWith(s => Value = s.Result, TaskContinuationOptions.OnlyOnRanToCompletion); + ScoringMode.BindValueChanged(_ => Value = scoreManager.GetTotalScore(score), true); } } From fee94236de92815f9d9b29375b56c5b7604ec132 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 21:36:31 +0900 Subject: [PATCH 017/207] Fix update-thread pauses --- .../BeatmapSet/Scores/ScoresContainer.cs | 39 +++++++----- osu.Game/Scoring/ScoreManager.cs | 19 ++++++ osu.Game/Screens/Ranking/ScorePanelList.cs | 62 +++++++++++-------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 30 ++++++--- 4 files changed, 99 insertions(+), 51 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 1b7e152c07..8e9a9eb684 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Game.Online.API.Requests.Responses; using osu.Game.Beatmaps; using osu.Game.Online.API; @@ -49,36 +51,41 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private GetScoresRequest getScoresRequest; + private CancellationTokenSource loadCancellationSource; + protected APILegacyScores Scores { set => Schedule(() => { + loadCancellationSource?.Cancel(); + loadCancellationSource = new CancellationTokenSource(); + topScoresContainer.Clear(); + scoreTable.ClearScores(); + scoreTable.Hide(); if (value?.Scores.Any() != true) - { - scoreTable.ClearScores(); - scoreTable.Hide(); return; - } - var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)) - .OrderByDescending(s => scoreManager.GetTotalScore(s)) - .ThenBy(s => s.OnlineScoreID) - .ToList(); + scoreManager.GetOrderedScoresAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => Schedule(() => + { + if (loadCancellationSource.IsCancellationRequested) + return; - var topScore = scoreInfos.First(); + var topScore = ordered.Result.First(); - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); - scoreTable.Show(); + scoreTable.DisplayScores(ordered.Result, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); + scoreTable.Show(); - var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); + var userScore = value.UserScore; + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); - topScoresContainer.Add(new DrawableTopScore(topScore)); + topScoresContainer.Add(new DrawableTopScore(topScore)); - if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); + if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); + }), TaskContinuationOptions.OnlyOnRanToCompletion); }); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8cf1c85956..71edc65fcc 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -106,6 +106,25 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); + public async Task GetOrderedScoresAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) + { + var difficultyCache = difficulties?.Invoke(); + + if (difficultyCache == null) + return orderByTotalScore(scores); + + // Compute difficulties asynchronously first to prevent blocks on the main thread. + foreach (var s in scores) + { + await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return orderByTotalScore(scores); + + ScoreInfo[] orderByTotalScore(IEnumerable incoming) => incoming.OrderByDescending(GetTotalScore).ThenBy(s => s.OnlineScoreID).ToArray(); + } + /// /// Retrieves a bindable that represents the total score of a . /// diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index ba90585ab5..e319e824a1 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -61,6 +62,10 @@ namespace osu.Game.Screens.Ranking public readonly Bindable SelectedScore = new Bindable(); + [Resolved] + private ScoreManager scoreManager { get; set; } + + private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -115,32 +120,33 @@ namespace osu.Game.Screens.Ranking }; }); - flow.Add(panel.CreateTrackingContainer().With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - })); + scoreManager.GetOrderedScoresAsync(new[] { score }) + .ContinueWith(_ => Schedule(() => + { + flow.Add(panel.CreateTrackingContainer().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + })); - if (IsLoaded) - { - if (SelectedScore.Value == score) - { - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) - { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; - } - } - } + if (SelectedScore.Value == score) + { + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } + } + })); return panel; } @@ -286,6 +292,12 @@ namespace osu.Game.Screens.Ranking return base.OnKeyDown(e); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + loadCancellationSource?.Cancel(); + } + private class Flow : FillFlowContainer { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 0276ae8b2a..54ec42127d 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -66,6 +68,9 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private ScoreManager scoreManager { get; set; } + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + [Resolved] private IBindable ruleset { get; set; } @@ -120,8 +125,13 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; + private CancellationTokenSource loadCancellationSource; + protected override APIRequest FetchScores(Action> scoresCallback) { + loadCancellationSource?.Cancel(); + loadCancellationSource = new CancellationTokenSource(); + if (Beatmap == null) { PlaceholderState = PlaceholderState.NoneSelected; @@ -146,11 +156,8 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - Scores = scores.OrderByDescending(s => scoreManager.GetTotalScore(s)) - .ThenBy(s => s.OnlineScoreID) - .ToArray(); - - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + scoreManager.GetOrderedScoresAsync(scores.ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); return null; } @@ -185,12 +192,15 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoresCallback?.Invoke( - r.Scores.Select(s => s.CreateScoreInfo(rulesets)) - .OrderByDescending(s => scoreManager.GetTotalScore(s)) - .ThenBy(s => s.OnlineScoreID)); + scoreManager.GetOrderedScoresAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => Schedule(() => + { + if (loadCancellationSource.IsCancellationRequested) + return; - TopScore = r.UserScore?.CreateScoreInfo(rulesets); + scoresCallback?.Invoke(ordered.Result); + TopScore = r.UserScore?.CreateScoreInfo(rulesets); + }), TaskContinuationOptions.OnlyOnRanToCompletion); }; return req; From 999386da2919a022132970344a2d089f192c9d2c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 21:47:49 +0900 Subject: [PATCH 018/207] Cleanup --- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 +-- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 1d9d2e6b14..4f8b27602b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Threading; -using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -247,7 +246,7 @@ namespace osu.Game.Online.Leaderboards private readonly IBindable apiState = new Bindable(); [BackgroundDependencyLoader] - private void load(OsuConfigManager configManager) + private void load() { if (api != null) apiState.BindTo(api.State); diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9a0bc35bb3..934b905a1a 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -198,8 +198,8 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), + Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.Numeric.With(size: 23), - Current = scoreManager.GetBindableTotalScoreString(score) }, RankContainer = new Container { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 9497ed9d35..a154016824 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -151,8 +151,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium), Current = scoreManager.GetBindableTotalScoreString(score), + Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 8e9a9eb684..a5a373467e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -182,7 +182,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuConfigManager config) + private void load(OverlayColourProvider colourProvider) { background.Colour = colourProvider.Background5; From 88fc53200ed880b5836d1e6c1497e33dd8bb6316 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 15:41:52 +0900 Subject: [PATCH 019/207] Refactor --- .../BeatmapSet/Scores/ScoresContainer.cs | 3 +- osu.Game/Scoring/ScoreManager.cs | 36 ++++++++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 56 ++++++++++--------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 4 +- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index a5a373467e..fb1769fbe1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -14,7 +14,6 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Bindables; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -67,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (value?.Scores.Any() != true) return; - scoreManager.GetOrderedScoresAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 71edc65fcc..6fccf80b6c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -34,7 +34,6 @@ namespace osu.Game.Scoring protected override string ImportFromStablePath => Path.Combine("Data", "r"); - private readonly Bindable scoringMode = new Bindable(); private readonly RulesetStore rulesets; private readonly Func beatmaps; @@ -52,8 +51,6 @@ namespace osu.Game.Scoring this.beatmaps = beatmaps; this.difficulties = difficulties; this.configManager = configManager; - - configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -106,14 +103,20 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); - public async Task GetOrderedScoresAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) + /// + /// Orders an array of s by total score. + /// + /// The array of s to reorder. + /// A to cancel the process. + /// The given ordered by decreasing total score. + public async Task OrderByTotalScoreAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) { var difficultyCache = difficulties?.Invoke(); if (difficultyCache == null) return orderByTotalScore(scores); - // Compute difficulties asynchronously first to prevent blocks on the main thread. + // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. foreach (var s in scores) { await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); @@ -122,7 +125,7 @@ namespace osu.Game.Scoring return orderByTotalScore(scores); - ScoreInfo[] orderByTotalScore(IEnumerable incoming) => incoming.OrderByDescending(GetTotalScore).ThenBy(s => s.OnlineScoreID).ToArray(); + ScoreInfo[] orderByTotalScore(IEnumerable incoming) => incoming.OrderByDescending(s => GetTotalScore(s)).ThenBy(s => s.OnlineScoreID).ToArray(); } /// @@ -150,9 +153,22 @@ namespace osu.Game.Scoring /// The bindable containing the formatted total score string. public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); - public long GetTotalScore(ScoreInfo score) => GetTotalScoreAsync(score).Result; + /// + /// Retrieves the total score of a in the given . + /// + /// The to calculate the total score of. + /// The to return the total score as. + /// The total score. + public long GetTotalScore(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) => GetTotalScoreAsync(score).Result; - public async Task GetTotalScoreAsync(ScoreInfo score, CancellationToken cancellationToken = default) + /// + /// Retrieves the total score of a in the given . + /// + /// The to calculate the total score of. + /// The to return the total score as. + /// A to cancel the process. + /// The total score. + public async Task GetTotalScoreAsync(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { if (score.Beatmap == null) return score.TotalScore; @@ -204,7 +220,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.GetScore(scoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + return (long)Math.Round(scoreProcessor.GetScore(mode, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } /// @@ -221,7 +237,7 @@ namespace osu.Game.Scoring /// The . public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) { - ScoringMode.BindValueChanged(_ => Value = scoreManager.GetTotalScore(score), true); + ScoringMode.BindValueChanged(mode => Value = scoreManager.GetTotalScore(score, mode.NewValue), true); } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e319e824a1..b0c06ebe6a 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Scoring; using osuTK; @@ -65,6 +66,9 @@ namespace osu.Game.Screens.Ranking [Resolved] private ScoreManager scoreManager { get; set; } + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); private readonly Flow flow; private readonly Scroll scroll; @@ -120,33 +124,33 @@ namespace osu.Game.Screens.Ranking }; }); - scoreManager.GetOrderedScoresAsync(new[] { score }) - .ContinueWith(_ => Schedule(() => - { - flow.Add(panel.CreateTrackingContainer().With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - })); + difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods) + .ContinueWith(_ => Schedule(() => + { + flow.Add(panel.CreateTrackingContainer().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + })); - if (SelectedScore.Value == score) - { - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) - { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; - } - } - })); + if (SelectedScore.Value == score) + { + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } + } + })); return panel; } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 54ec42127d..7820264505 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - scoreManager.GetOrderedScoresAsync(scores.ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); return null; @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.GetOrderedScoresAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) From ab538dc3dd4474961c2235c46d4223892e29cf60 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 20:30:26 +0900 Subject: [PATCH 020/207] Fix param not passed through --- osu.Game/Scoring/ScoreManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6fccf80b6c..9b94e34f75 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -159,7 +159,7 @@ namespace osu.Game.Scoring /// The to calculate the total score of. /// The to return the total score as. /// The total score. - public long GetTotalScore(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) => GetTotalScoreAsync(score).Result; + public long GetTotalScore(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) => GetTotalScoreAsync(score, mode).Result; /// /// Retrieves the total score of a in the given . From f7c1177cc9ec585583a549e81e834aa581819e64 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 20:35:06 +0900 Subject: [PATCH 021/207] Fix ScorePanelList nullref when scores are added too soon --- .../Visual/Ranking/TestSceneScorePanelList.cs | 16 ++++ osu.Game/Screens/Ranking/ScorePanelList.cs | 95 ++++++++++--------- 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index e65dcb19b1..f330e99d55 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -182,6 +182,22 @@ namespace osu.Game.Tests.Visual.Ranking assertExpandedPanelCentred(); } + [Test] + public void TestAddScoreImmediately() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => + { + var newList = new ScorePanelList { SelectedScore = { Value = score } }; + newList.AddScore(score); + return newList; + }); + + assertScoreState(score, true); + assertExpandedPanelCentred(); + } + private void createListStep(Func creationFunc) { AddStep("create list", () => Child = list = creationFunc().With(d => diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index b0c06ebe6a..711e0d60ec 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -100,6 +101,9 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); + foreach (var d in flow) + displayScore(d); + SelectedScore.BindValueChanged(selectedScoreChanged, true); } @@ -124,37 +128,56 @@ namespace osu.Game.Screens.Ranking }; }); - difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods) - .ContinueWith(_ => Schedule(() => - { - flow.Add(panel.CreateTrackingContainer().With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - })); + var trackingContainer = panel.CreateTrackingContainer().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + d.Hide(); + }); - if (SelectedScore.Value == score) - { - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) - { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; - } - } - })); + flow.Add(trackingContainer); + + if (IsLoaded) + displayScore(trackingContainer); return panel; } + private void displayScore(ScorePanelTrackingContainer trackingContainer) + { + if (!IsLoaded) + return; + + var score = trackingContainer.Panel.Score; + + // Calculating score can take a while in extreme scenarios, so only display scores after the process completes. + scoreManager.GetTotalScoreAsync(score) + .ContinueWith(totalScore => Schedule(() => + { + flow.SetLayoutPosition(trackingContainer, totalScore.Result); + + trackingContainer.Show(); + + if (SelectedScore.Value == score) + { + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } + } + }), TaskContinuationOptions.OnlyOnRanToCompletion); + } + /// /// Brings a to the centre of the screen and expands it. /// @@ -306,28 +329,8 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - [Resolved] - private ScoreManager scoreManager { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - foreach (var c in Children) - SetLayoutPosition(c, scoreManager.GetTotalScore(c.Panel.Score)); - } - public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); - public override void Add(ScorePanelTrackingContainer drawable) - { - Debug.Assert(drawable != null); - - base.Add(drawable); - - SetLayoutPosition(drawable, scoreManager?.GetTotalScore(drawable.Panel.Score) ?? 0); - } - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineScoreID); From df7480e68cc4d8d3f3fcec7a70e2b4c4db0b4854 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 20:56:23 +0900 Subject: [PATCH 022/207] Fix bindable implementation being synchronous --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 49 +++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 184a2e59da..29815ce9ff 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler)); return dependencies; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 3f9e0048dd..98482601ee 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler)); beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9b94e34f75..2cda8959f4 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -13,6 +13,7 @@ using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; @@ -36,6 +37,7 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; + private readonly Scheduler scheduler; [CanBeNull] private readonly Func difficulties; @@ -43,12 +45,13 @@ namespace osu.Game.Scoring [CanBeNull] private readonly OsuConfigManager configManager; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, - Func difficulties = null, OsuConfigManager configManager = null) + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, Scheduler scheduler, + IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; this.beatmaps = beatmaps; + this.scheduler = scheduler; this.difficulties = difficulties; this.configManager = configManager; } @@ -125,7 +128,13 @@ namespace osu.Game.Scoring return orderByTotalScore(scores); - ScoreInfo[] orderByTotalScore(IEnumerable incoming) => incoming.OrderByDescending(s => GetTotalScore(s)).ThenBy(s => s.OnlineScoreID).ToArray(); + ScoreInfo[] orderByTotalScore(IEnumerable incoming) + { + // We're calling .Result, but this should not be a blocking call due to the above GetDifficultyAsync() calls. + return incoming.OrderByDescending(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken).Result) + .ThenBy(s => s.OnlineScoreID) + .ToArray(); + } } /// @@ -136,7 +145,7 @@ namespace osu.Game.Scoring /// /// The to retrieve the bindable for. /// The bindable containing the total score. - public Bindable GetBindableTotalScore(ScoreInfo score) + public Bindable GetBindableTotalScore([NotNull] ScoreInfo score) { var bindable = new TotalScoreBindable(score, this); configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); @@ -151,15 +160,21 @@ namespace osu.Game.Scoring /// /// The to retrieve the bindable for. /// The bindable containing the formatted total score string. - public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); + public Bindable GetBindableTotalScoreString([NotNull] ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); /// /// Retrieves the total score of a in the given . + /// The score is returned in a callback that is run on the update thread. /// /// The to calculate the total score of. + /// The callback to be invoked with the total score. /// The to return the total score as. - /// The total score. - public long GetTotalScore(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) => GetTotalScoreAsync(score, mode).Result; + /// A to cancel the process. + public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) + { + GetTotalScoreAsync(score, mode, cancellationToken) + .ContinueWith(s => scheduler.Add(() => callback(s.Result)), TaskContinuationOptions.OnlyOnRanToCompletion); + } /// /// Retrieves the total score of a in the given . @@ -168,7 +183,7 @@ namespace osu.Game.Scoring /// The to return the total score as. /// A to cancel the process. /// The total score. - public async Task GetTotalScoreAsync(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) + public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { if (score.Beatmap == null) return score.TotalScore; @@ -230,6 +245,11 @@ namespace osu.Game.Scoring { public readonly Bindable ScoringMode = new Bindable(); + private readonly ScoreInfo score; + private readonly ScoreManager scoreManager; + + private CancellationTokenSource difficultyCalculationCancellationSource; + /// /// Creates a new . /// @@ -237,7 +257,18 @@ namespace osu.Game.Scoring /// The . public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) { - ScoringMode.BindValueChanged(mode => Value = scoreManager.GetTotalScore(score, mode.NewValue), true); + this.score = score; + this.scoreManager = scoreManager; + + ScoringMode.BindValueChanged(onScoringModeChanged, true); + } + + private void onScoringModeChanged(ValueChangedEvent mode) + { + difficultyCalculationCancellationSource?.Cancel(); + difficultyCalculationCancellationSource = new CancellationTokenSource(); + + scoreManager.GetTotalScore(score, s => Value = s, mode.NewValue, difficultyCalculationCancellationSource.Token); } } From f14d66aafce778d86e2cc203afd0828d4c60293c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Sep 2021 10:35:00 +0900 Subject: [PATCH 023/207] Commit missed line --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 69f6bc1b7b..16e3cdbfda 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -262,7 +262,7 @@ namespace osu.Game dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true)); // this should likely be moved to ArchiveModelManager when another case appears where it is necessary From 59ca69e41f2e594acc7a398094bb5f99fbd0e27e Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 18:16:57 +0100 Subject: [PATCH 024/207] add /chat command --- osu.Game/Online/Chat/ChannelManager.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1937019ef6..f58f1ff40c 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -256,8 +256,21 @@ namespace osu.Game.Online.Chat JoinChannel(channel); break; + case "chat": + if (string.IsNullOrWhiteSpace(content)) + { + target.AddNewMessages(new ErrorMessage("Usage: /chat [user]")); + break; + } + + var request = new GetUserRequest(content); + request.Success += OpenPrivateChannel; + request.Failure += _ => target.AddNewMessages(new ErrorMessage("User not found.")); + api.Queue(request); + break; + case "help": - target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np")); + target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /chat [user], /np")); break; default: From e8fb5d2e663be713cfba16e967053952305f6e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 16:07:24 +0200 Subject: [PATCH 025/207] Add non-functional difficulty switcher to menu --- osu.Game/Screens/Edit/Editor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 57c78f3c65..a9fcf29c51 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -703,6 +703,13 @@ namespace osu.Game.Screens.Edit if (RuntimeInfo.IsDesktop) fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap)); + fileMenuItems.Add(new EditorMenuItemSpacer()); + + var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; + var difficultyItems = beatmapSet.Beatmaps.Select(b => new EditorMenuItem(b.Version ?? "(unnamed)")).ToList(); + + fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); + fileMenuItems.Add(new EditorMenuItemSpacer()); fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); return fileMenuItems; From 90f0b6874f4fdda9e3b1dee6d7d2dec883e4cfb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 16:10:49 +0200 Subject: [PATCH 026/207] Highlight current difficulty in switcher --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a9fcf29c51..cb78a19636 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -706,7 +706,10 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; - var difficultyItems = beatmapSet.Beatmaps.Select(b => new EditorMenuItem(b.Version ?? "(unnamed)")).ToList(); + var difficultyItems = beatmapSet.Beatmaps.Select(b => new ToggleMenuItem(b.Version ?? "(unnamed)") + { + State = { Value = playableBeatmap.BeatmapInfo.Equals(b) } + }).ToList(); fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); From 7befd030dfe3f8fb58c1886696ce07d737743b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 16:28:32 +0200 Subject: [PATCH 027/207] Minimal working example of switching difficulties --- osu.Game/Screens/Edit/Editor.cs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index cb78a19636..c7ac232f58 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -35,7 +35,9 @@ using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; +using osu.Game.Screens.Menu; using osu.Game.Screens.Play; +using osu.Game.Screens.Select; using osu.Game.Users; using osuTK.Graphics; using osuTK.Input; @@ -101,6 +103,9 @@ namespace osu.Game.Screens.Edit [Resolved] private MusicController music { get; set; } + [Resolved(CanBeNull = true)] + private OsuGame game { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, OsuConfigManager config) { @@ -706,10 +711,7 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; - var difficultyItems = beatmapSet.Beatmaps.Select(b => new ToggleMenuItem(b.Version ?? "(unnamed)") - { - State = { Value = playableBeatmap.BeatmapInfo.Equals(b) } - }).ToList(); + var difficultyItems = beatmapSet.Beatmaps.Select(createDifficultyMenuItem).ToList(); fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); @@ -718,6 +720,28 @@ namespace osu.Game.Screens.Edit return fileMenuItems; } + private ToggleMenuItem createDifficultyMenuItem(BeatmapInfo b) + { + bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(b); + + var menuItem = new ToggleMenuItem(b.Version ?? "(unnamed)") { State = { Value = isCurrentDifficulty }, }; + + if (!isCurrentDifficulty) + { + menuItem.Action.Value = () => + { + game?.PerformFromScreen(screen => + { + var osuScreen = (OsuScreen)screen; + osuScreen.Beatmap.Value = beatmapManager.GetWorkingBeatmap(b); + screen.Push(new Editor()); + }, new[] { typeof(MainMenu), typeof(SongSelect) }); + }; + } + + return menuItem; + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); From fe2520c599b59e5f66e0aefba32c167c98f72052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 16:59:28 +0200 Subject: [PATCH 028/207] Add intermediary screen to avoid going back to menus --- osu.Game/Screens/Edit/Editor.cs | 21 +++++++++++++++------ osu.Game/Screens/Edit/EditorLoader.cs | 27 +++++++++++++++++++++++++++ osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 4 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorLoader.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c7ac232f58..bc6d26135b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -35,9 +36,7 @@ using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; -using osu.Game.Screens.Menu; using osu.Game.Screens.Play; -using osu.Game.Screens.Select; using osu.Game.Users; using osuTK.Graphics; using osuTK.Input; @@ -77,6 +76,9 @@ namespace osu.Game.Screens.Edit private Container screenContainer; + [CanBeNull] + private readonly EditorLoader loader; + private EditorScreen currentScreen; private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); @@ -106,6 +108,11 @@ namespace osu.Game.Screens.Edit [Resolved(CanBeNull = true)] private OsuGame game { get; set; } + public Editor(EditorLoader loader = null) + { + this.loader = loader; + } + [BackgroundDependencyLoader] private void load(OsuColour colours, OsuConfigManager config) { @@ -730,12 +737,14 @@ namespace osu.Game.Screens.Edit { menuItem.Action.Value = () => { + if (loader != null) + loader.ValidForResume = true; + game?.PerformFromScreen(screen => { - var osuScreen = (OsuScreen)screen; - osuScreen.Beatmap.Value = beatmapManager.GetWorkingBeatmap(b); - screen.Push(new Editor()); - }, new[] { typeof(MainMenu), typeof(SongSelect) }); + if (screen != null && screen == loader) + loader.PushEditor(); + }, new[] { typeof(EditorLoader) }); }; } diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs new file mode 100644 index 0000000000..5c8d0f5b16 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Screens; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.Edit +{ + /// + /// Transition screen for the editor. + /// Used to avoid backing out to main menu/song select when switching difficulties from within the editor. + /// + public class EditorLoader : ScreenWithBeatmapBackground + { + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + PushEditor(); + } + + public void PushEditor() + { + this.Push(new Editor(this)); + ValidForResume = false; + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 1d0182a945..8b2ec43e3e 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu OnEdit = delegate { Beatmap.SetDefault(); - this.Push(new Editor()); + this.Push(new EditorLoader()); }, OnSolo = loadSoloSongSelect, OnMultiplayer = () => this.Push(new Multiplayer()), diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b3d715e580..f11f9fd614 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -349,7 +349,7 @@ namespace osu.Game.Screens.Select throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled"); Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce); - this.Push(new Editor()); + this.Push(new EditorLoader()); } /// From c397cc2027ceb14e216daa075263e2f52588de0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 17:26:09 +0200 Subject: [PATCH 029/207] Restructure proof of concept --- .../Components/Menus/DifficultyMenuItem.cs | 24 +++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 30 ++++++++----------- osu.Game/Screens/Edit/EditorLoader.cs | 11 ++++++- 3 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs new file mode 100644 index 0000000000..74b18f63ab --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Components.Menus +{ + public class DifficultyMenuItem : StatefulMenuItem + { + public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc) + : base(beatmapInfo.Version ?? "(unnamed)", null) + { + State.Value = selected; + + if (!selected) + Action.Value = () => difficultyChangeFunc.Invoke(beatmapInfo); + } + + public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null; + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bc6d26135b..f30f92b602 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -727,28 +727,24 @@ namespace osu.Game.Screens.Edit return fileMenuItems; } - private ToggleMenuItem createDifficultyMenuItem(BeatmapInfo b) + private DifficultyMenuItem createDifficultyMenuItem(BeatmapInfo beatmapInfo) { - bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(b); + bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmapInfo); + return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, switchToDifficulty); + } - var menuItem = new ToggleMenuItem(b.Version ?? "(unnamed)") { State = { Value = isCurrentDifficulty }, }; + private void switchToDifficulty(BeatmapInfo beatmapInfo) + { + if (loader != null) + loader.ValidForResume = true; - if (!isCurrentDifficulty) + game?.PerformFromScreen(screen => { - menuItem.Action.Value = () => - { - if (loader != null) - loader.ValidForResume = true; + if (screen == null || screen != loader) + return; - game?.PerformFromScreen(screen => - { - if (screen != null && screen == loader) - loader.PushEditor(); - }, new[] { typeof(EditorLoader) }); - }; - } - - return menuItem; + loader.PushEditor(beatmapInfo); + }, new[] { typeof(EditorLoader) }); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 5c8d0f5b16..45f2d1bffd 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -1,7 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit @@ -12,14 +15,20 @@ namespace osu.Game.Screens.Edit /// public class EditorLoader : ScreenWithBeatmapBackground { + [Resolved] + private BeatmapManager beatmapManager { get; set; } + public override void OnEntering(IScreen last) { base.OnEntering(last); PushEditor(); } - public void PushEditor() + public void PushEditor([CanBeNull] BeatmapInfo beatmapInfo = null) { + if (beatmapInfo != null) + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + this.Push(new Editor(this)); ValidForResume = false; } From a9403b65b3eccda6ac7f14826bb90efff5c71555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 17:53:57 +0200 Subject: [PATCH 030/207] Eliminate dependency on OsuGame --- osu.Game/Screens/Edit/Editor.cs | 17 +++++------------ osu.Game/Screens/Edit/EditorLoader.cs | 4 ++-- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f30f92b602..8e845bac05 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -105,9 +105,6 @@ namespace osu.Game.Screens.Edit [Resolved] private MusicController music { get; set; } - [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } - public Editor(EditorLoader loader = null) { this.loader = loader; @@ -735,16 +732,12 @@ namespace osu.Game.Screens.Edit private void switchToDifficulty(BeatmapInfo beatmapInfo) { - if (loader != null) - loader.ValidForResume = true; + if (loader == null) + return; - game?.PerformFromScreen(screen => - { - if (screen == null || screen != loader) - return; - - loader.PushEditor(beatmapInfo); - }, new[] { typeof(EditorLoader) }); + loader.ValidForResume = true; + this.Exit(); + loader.PushEditor(beatmapInfo); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 45f2d1bffd..011f40419c 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -24,13 +24,13 @@ namespace osu.Game.Screens.Edit PushEditor(); } - public void PushEditor([CanBeNull] BeatmapInfo beatmapInfo = null) + public void PushEditor([CanBeNull] BeatmapInfo beatmapInfo = null) => Schedule(() => { if (beatmapInfo != null) Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); this.Push(new Editor(this)); ValidForResume = false; - } + }); } } From c72523bc14afe2b96ad0fb3a298b0edc9d8f5a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 18:32:38 +0200 Subject: [PATCH 031/207] Add basic test for difficulty switching --- .../Editing/TestSceneDifficultySwitching.cs | 91 +++++++++++++++++++ .../Components/Menus/DifficultyMenuItem.cs | 3 + 2 files changed, 94 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs new file mode 100644 index 0000000000..4ac2e9cfef --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Tests.Beatmaps.IO; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneDifficultySwitching : ScreenTestScene + { + private BeatmapSetInfo importedBeatmapSet; + + [Resolved] + private OsuGameBase game { get; set; } + + private Editor editor; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [SetUpSteps] + public void SetUp() + { + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result); + + AddStep("set current beatmap", () => Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First())); + AddStep("push loader", () => Stack.Push(new EditorLoader())); + + AddUntilStep("wait for editor to load", () => Stack.CurrentScreen is Editor); + AddStep("store editor", () => editor = (Editor)Stack.CurrentScreen); + } + + [Test] + public void TestBasicSwitch() + { + BeatmapInfo targetDifficulty = null; + + AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); + switchToDifficulty(() => targetDifficulty); + confirmEditingBeatmap(() => targetDifficulty); + + AddStep("exit editor", () => Stack.Exit()); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + + private void switchToDifficulty(Func difficulty) + { + AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any()); + AddStep("open file menu", () => + { + var menuBar = editor.ChildrenOfType().Single(); + var fileMenu = menuBar.ChildrenOfType().First(); + InputManager.MoveMouseTo(fileMenu); + InputManager.Click(MouseButton.Left); + }); + + AddStep("open difficulty menu", () => + { + var difficultySelector = + editor.ChildrenOfType().Single(item => item.Item.Text.Value.ToString().Contains("Change difficulty")); + InputManager.MoveMouseTo(difficultySelector); + }); + AddWaitStep("wait for open", 3); + + AddStep("switch to target difficulty", () => + { + var difficultyMenuItem = + editor.ChildrenOfType() + .Last(item => item.Item is DifficultyMenuItem difficultyItem && difficultyItem.Beatmap.Equals(difficulty.Invoke())); + InputManager.MoveMouseTo(difficultyMenuItem); + InputManager.Click(MouseButton.Left); + }); + } + + private void confirmEditingBeatmap(Func targetDifficulty) + { + AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke())); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs index 74b18f63ab..5f9b72447b 100644 --- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -10,9 +10,12 @@ namespace osu.Game.Screens.Edit.Components.Menus { public class DifficultyMenuItem : StatefulMenuItem { + public BeatmapInfo Beatmap { get; } + public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc) : base(beatmapInfo.Version ?? "(unnamed)", null) { + Beatmap = beatmapInfo; State.Value = selected; if (!selected) From 382269b362061da851565b88092c08b923eeadbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 19:11:46 +0200 Subject: [PATCH 032/207] Test staying on same difficulty due to unsaved changes --- .../Editing/TestSceneDifficultySwitching.cs | 37 ++++++++++++++++++- osu.Game/Screens/Edit/Editor.cs | 13 ++++++- osu.Game/Screens/Edit/EditorLoader.cs | 25 ++++++++++--- osu.Game/Screens/Edit/PromptForSaveDialog.cs | 3 +- 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 4ac2e9cfef..60c2518a7e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Dialog; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Tests.Beatmaps.IO; @@ -35,8 +36,9 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set current beatmap", () => Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First())); AddStep("push loader", () => Stack.Push(new EditorLoader())); - AddUntilStep("wait for editor to load", () => Stack.CurrentScreen is Editor); + AddUntilStep("wait for editor push", () => Stack.CurrentScreen is Editor); AddStep("store editor", () => editor = (Editor)Stack.CurrentScreen); + AddUntilStep("wait for editor to load", () => editor.IsLoaded); } [Test] @@ -53,6 +55,39 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } + [Test] + public void TestPreventSwitchDueToUnsavedChanges() + { + BeatmapInfo targetDifficulty = null; + PromptForSaveDialog saveDialog = null; + + AddStep("remove first hitobject", () => + { + var editorBeatmap = editor.ChildrenOfType().Single(); + editorBeatmap.RemoveAt(0); + }); + + AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); + switchToDifficulty(() => targetDifficulty); + + AddUntilStep("prompt for save dialog shown", () => + { + saveDialog = this.ChildrenOfType().Single(); + return saveDialog != null; + }); + AddStep("continue editing", () => + { + var continueButton = saveDialog.ChildrenOfType().Last(); + continueButton.TriggerClick(); + }); + + confirmEditingBeatmap(() => importedBeatmapSet.Beatmaps.First()); + + AddRepeatStep("exit editor forcefully", () => Stack.Exit(), 2); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + private void switchToDifficulty(Func difficulty) { AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any()); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8e845bac05..df08e0a017 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Edit if (isNewBeatmap || HasUnsavedChanges) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave, cancelPendingDifficultySwitch)); return true; } } @@ -737,7 +737,16 @@ namespace osu.Game.Screens.Edit loader.ValidForResume = true; this.Exit(); - loader.PushEditor(beatmapInfo); + loader.ScheduleDifficultySwitch(beatmapInfo); + } + + private void cancelPendingDifficultySwitch() + { + if (loader == null) + return; + + loader.ValidForResume = false; + loader.CancelDifficultySwitch(); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 011f40419c..d913076e83 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Screens.Play; @@ -18,19 +19,31 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } + [CanBeNull] + private ScheduledDelegate scheduledDifficultySwitch; + public override void OnEntering(IScreen last) { base.OnEntering(last); - PushEditor(); + pushEditor(); } - public void PushEditor([CanBeNull] BeatmapInfo beatmapInfo = null) => Schedule(() => + private void pushEditor() { - if (beatmapInfo != null) - Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); - this.Push(new Editor(this)); ValidForResume = false; - }); + } + + public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) + { + CancelDifficultySwitch(); + scheduledDifficultySwitch = Schedule(() => + { + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + pushEditor(); + }); + } + + public void CancelDifficultySwitch() => scheduledDifficultySwitch?.Cancel(); } } diff --git a/osu.Game/Screens/Edit/PromptForSaveDialog.cs b/osu.Game/Screens/Edit/PromptForSaveDialog.cs index 16504b47bd..e308a9533d 100644 --- a/osu.Game/Screens/Edit/PromptForSaveDialog.cs +++ b/osu.Game/Screens/Edit/PromptForSaveDialog.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Edit { public class PromptForSaveDialog : PopupDialog { - public PromptForSaveDialog(Action exit, Action saveAndExit) + public PromptForSaveDialog(Action exit, Action saveAndExit, Action cancel) { HeaderText = "Did you want to save your changes?"; @@ -30,6 +30,7 @@ namespace osu.Game.Screens.Edit new PopupDialogCancelButton { Text = @"Oops, continue editing", + Action = cancel }, }; } From 74a129dc27fd3c0bc6ce6e5919106f7a1b9e45ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 19:20:18 +0200 Subject: [PATCH 033/207] Test switching difficulties after discarding changes --- .../Editing/TestSceneDifficultySwitching.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 60c2518a7e..ecf1fec850 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -88,6 +88,39 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } + [Test] + public void TestAllowSwitchAfterDiscardingUnsavedChanges() + { + BeatmapInfo targetDifficulty = null; + PromptForSaveDialog saveDialog = null; + + AddStep("remove first hitobject", () => + { + var editorBeatmap = editor.ChildrenOfType().Single(); + editorBeatmap.RemoveAt(0); + }); + + AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); + switchToDifficulty(() => targetDifficulty); + + AddUntilStep("prompt for save dialog shown", () => + { + saveDialog = this.ChildrenOfType().Single(); + return saveDialog != null; + }); + AddStep("discard changes", () => + { + var continueButton = saveDialog.ChildrenOfType().Single(); + continueButton.TriggerClick(); + }); + + confirmEditingBeatmap(() => targetDifficulty); + + AddStep("exit editor forcefully", () => Stack.Exit()); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + private void switchToDifficulty(Func difficulty) { AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any()); From 7012a1d9340a01011cbc0e3ef190f3f0ec8030d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 20:24:07 +0200 Subject: [PATCH 034/207] Fix issues with main menu -> editor loader transition --- .../Editing/TestSceneDifficultySwitching.cs | 15 +++++++++++-- osu.Game/Screens/Edit/EditorLoader.cs | 22 ++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index ecf1fec850..0f3d413a7d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Dialog; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Screens.Menu; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; @@ -19,15 +20,25 @@ namespace osu.Game.Tests.Visual.Editing public class TestSceneDifficultySwitching : ScreenTestScene { private BeatmapSetInfo importedBeatmapSet; + private Editor editor; + + // required for screen transitions to work properly + // (see comment in EditorLoader.LogoArriving). + [Cached] + private OsuLogo logo = new OsuLogo + { + Alpha = 0 + }; [Resolved] private OsuGameBase game { get; set; } - private Editor editor; - [Resolved] private BeatmapManager beatmaps { get; set; } + [BackgroundDependencyLoader] + private void load() => Add(logo); + [SetUpSteps] public void SetUp() { diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index d913076e83..07eb5e5b1b 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -3,10 +3,11 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Screens.Play; +using osu.Game.Screens.Menu; namespace osu.Game.Screens.Edit { @@ -14,18 +15,29 @@ namespace osu.Game.Screens.Edit /// Transition screen for the editor. /// Used to avoid backing out to main menu/song select when switching difficulties from within the editor. /// - public class EditorLoader : ScreenWithBeatmapBackground + public class EditorLoader : OsuScreen { + public override float BackgroundParallaxAmount => 0.1f; + + public override bool AllowBackButton => false; + + public override bool HideOverlaysOnEnter => true; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + [Resolved] private BeatmapManager beatmapManager { get; set; } [CanBeNull] private ScheduledDelegate scheduledDifficultySwitch; - public override void OnEntering(IScreen last) + protected override void LogoArriving(OsuLogo logo, bool resuming) { - base.OnEntering(last); - pushEditor(); + base.LogoArriving(logo, resuming); + // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. + // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current + // before enqueueing this screen's LogoArriving onto the logo animation sequence. + logo.Delay(300).Schedule(pushEditor); } private void pushEditor() From d6a47fd99ceeb2f4a707da8c11ed66c940fc787e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 21:00:19 +0200 Subject: [PATCH 035/207] Sort difficulties by ruleset and star rating in menu --- osu.Game/Screens/Edit/Editor.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index df08e0a017..7d0b936df9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -715,7 +715,17 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; - var difficultyItems = beatmapSet.Beatmaps.Select(createDifficultyMenuItem).ToList(); + + var difficultyItems = new List(); + + foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.RulesetID).OrderBy(group => group.Key)) + { + if (difficultyItems.Count > 0) + difficultyItems.Add(new EditorMenuItemSpacer()); + + foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarDifficulty)) + difficultyItems.Add(createDifficultyMenuItem(beatmap)); + } fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); From cb6cee9aea75901d4bcf230e5b1cf8d8f5c13c1a Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 21:10:08 +0100 Subject: [PATCH 036/207] add /query as alias of /chat --- osu.Game/Online/Chat/ChannelManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index f58f1ff40c..d72a050d84 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -257,6 +257,7 @@ namespace osu.Game.Online.Chat break; case "chat": + case "query": if (string.IsNullOrWhiteSpace(content)) { target.AddNewMessages(new ErrorMessage("Usage: /chat [user]")); From 5c385e84ea4988528ff628867c34705d22c889c3 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 21:20:19 +0100 Subject: [PATCH 037/207] wrong command name in query message --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index d72a050d84..d2500f0d7f 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -260,7 +260,7 @@ namespace osu.Game.Online.Chat case "query": if (string.IsNullOrWhiteSpace(content)) { - target.AddNewMessages(new ErrorMessage("Usage: /chat [user]")); + target.AddNewMessages(new ErrorMessage($"Usage: /{command} [user]")); break; } From 401d38fc051366cb26f3f25bf389decbf5a5bfec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Sep 2021 19:07:28 +0900 Subject: [PATCH 038/207] Fix possible nullref --- osu.Game/Screens/Ranking/ScorePanelList.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 711e0d60ec..4ac30ef4ed 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -301,6 +301,9 @@ namespace osu.Game.Screens.Ranking protected override bool OnKeyDown(KeyDownEvent e) { + if (expandedPanel == null) + return base.OnKeyDown(e); + var expandedPanelIndex = flow.GetPanelIndex(expandedPanel.Score); switch (e.Key) From 20100b8894d5a58af13e1351628e0366e6256ac5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Sep 2021 20:20:52 +0900 Subject: [PATCH 039/207] Fix a few test failures --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 2 ++ osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 8 +++++--- osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs | 5 +++++ .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 ++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 ++ 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 61d49e4018..b826683cd5 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -160,6 +160,8 @@ namespace osu.Game.Tests.Visual.Playlists Ruleset = { Value = new OsuRuleset().RulesetInfo } })); }); + + AddUntilStep("wait for load", () => resultsScreen.ChildrenOfType().FirstOrDefault()?.AllPanelsVisible == true); } private void waitForDisplay() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ba6b6bd529..34a9610804 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Ranking TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); - AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddStep("click expanded panel", () => { @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Ranking TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); - AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddStep("click expanded panel", () => { @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.Ranking TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); - AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); ScorePanel expandedPanel = null; ScorePanel contractedPanel = null; @@ -201,6 +201,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestFetchScoresAfterShowingStatistics() + { DelayedFetchResultsScreen screen = null; @@ -223,6 +224,7 @@ namespace osu.Game.Tests.Visual.Ranking TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddAssert("download button is disabled", () => !screen.ChildrenOfType().Last().Enabled.Value); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index f330e99d55..b2585c0509 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -159,6 +159,9 @@ namespace osu.Game.Tests.Visual.Ranking var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + firstScore.User.Username = "A"; + secondScore.User.Username = "B"; + createListStep(() => new ScorePanelList()); AddStep("add scores and select first", () => @@ -168,6 +171,8 @@ namespace osu.Game.Tests.Visual.Ranking list.SelectedScore.Value = firstScore; }); + AddUntilStep("wait for load", () => list.AllPanelsVisible); + assertScoreState(firstScore, true); assertScoreState(secondScore, false); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 98482601ee..8efee2723f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -158,14 +158,14 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID)); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s != scores[0])); } [Test] public void TestDeleteViaDatabase() { AddStep("delete top score", () => scoreManager.Delete(scores[0])); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID)); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s != scores[0])); } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4ac30ef4ed..6d65137984 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Ranking /// public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); + public bool AllPanelsVisible => flow.All(p => p.IsPresent); + /// /// The current scroll position. /// From bd7d6dd35d54ca11c11616ea1b5f97a74e87b2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Sep 2021 21:27:17 +0200 Subject: [PATCH 040/207] Rename method --- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7d0b936df9..d0b50c13e6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Edit if (isNewBeatmap || HasUnsavedChanges) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave, cancelPendingDifficultySwitch)); + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave, cancelExit)); return true; } } @@ -750,7 +750,7 @@ namespace osu.Game.Screens.Edit loader.ScheduleDifficultySwitch(beatmapInfo); } - private void cancelPendingDifficultySwitch() + private void cancelExit() { if (loader == null) return; From 2d59008f528f441cff81e9144b25bf208da04173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Sep 2021 21:30:50 +0200 Subject: [PATCH 041/207] Move screen management logic to `EditorLoader` --- osu.Game/Screens/Edit/Editor.cs | 19 ++----------------- osu.Game/Screens/Edit/EditorLoader.cs | 11 +++++++++-- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d0b50c13e6..1b9a94da58 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -740,24 +740,9 @@ namespace osu.Game.Screens.Edit return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, switchToDifficulty); } - private void switchToDifficulty(BeatmapInfo beatmapInfo) - { - if (loader == null) - return; + private void switchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo); - loader.ValidForResume = true; - this.Exit(); - loader.ScheduleDifficultySwitch(beatmapInfo); - } - - private void cancelExit() - { - if (loader == null) - return; - - loader.ValidForResume = false; - loader.CancelDifficultySwitch(); - } + private void cancelExit() => loader?.CancelPendingDifficultySwitch(); public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 07eb5e5b1b..fbb358d217 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -48,7 +48,10 @@ namespace osu.Game.Screens.Edit public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) { - CancelDifficultySwitch(); + scheduledDifficultySwitch?.Cancel(); + ValidForResume = true; + + this.MakeCurrent(); scheduledDifficultySwitch = Schedule(() => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); @@ -56,6 +59,10 @@ namespace osu.Game.Screens.Edit }); } - public void CancelDifficultySwitch() => scheduledDifficultySwitch?.Cancel(); + public void CancelPendingDifficultySwitch() + { + scheduledDifficultySwitch?.Cancel(); + ValidForResume = false; + } } } From 5b9f37702b72e69e1976e6091bb8754650149135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Sep 2021 21:31:59 +0200 Subject: [PATCH 042/207] Remove unnecessary delay before pushing editor from loader --- osu.Game/Screens/Edit/EditorLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index fbb358d217..92420fa7c0 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -3,7 +3,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -34,10 +33,11 @@ namespace osu.Game.Screens.Edit protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); + // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current // before enqueueing this screen's LogoArriving onto the logo animation sequence. - logo.Delay(300).Schedule(pushEditor); + pushEditor(); } private void pushEditor() From c9325cc41947c5c1b62afe5b3bc249b643f5527b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 14:15:23 +0900 Subject: [PATCH 043/207] Fix results screen test scene --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 1 + osu.Game/Screens/Ranking/ScorePanelList.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index b826683cd5..4bcc887b9f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -167,6 +167,7 @@ namespace osu.Game.Tests.Visual.Playlists private void waitForDisplay() { AddUntilStep("wait for request to complete", () => requestComplete); + AddUntilStep("wait for panels to be visible", () => resultsScreen.ChildrenOfType().FirstOrDefault()?.AllPanelsVisible == true); AddWaitStep("wait for display", 5); } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 6d65137984..a847df344f 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -40,12 +40,12 @@ namespace osu.Game.Screens.Ranking /// /// Whether this can be scrolled and is currently scrolled to the start. /// - public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= scroll_endpoint_distance; + public bool IsScrolledToStart => flow.Count > 0 && AllPanelsVisible && scroll.ScrollableExtent > 0 && scroll.Current <= scroll_endpoint_distance; /// /// Whether this can be scrolled and is currently scrolled to the end. /// - public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); + public bool IsScrolledToEnd => flow.Count > 0 && AllPanelsVisible && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); public bool AllPanelsVisible => flow.All(p => p.IsPresent); From 59aa4dabfda755ffac0a5e9bb4a58d371994394a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:33:58 +0900 Subject: [PATCH 044/207] Improve code around background screen handling to read better --- osu.Game/Screens/BackgroundScreenStack.cs | 12 +++++++++--- osu.Game/Screens/OsuScreen.cs | 9 +++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 294f23d2ac..9f562a618e 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -17,15 +17,21 @@ namespace osu.Game.Screens Origin = Anchor.Centre; } - public void Push(BackgroundScreen screen) + /// + /// Attempt to push a new background screen to this stack. + /// + /// The screen to attempt to push. + /// Whether the push succeeded. For example, if the existing screen was already of the correct type this will return false. + public bool Push(BackgroundScreen screen) { if (screen == null) - return; + return false; if (EqualityComparer.Default.Equals((BackgroundScreen)CurrentScreen, screen)) - return; + return false; base.Push(screen); + return true; } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index e3fe14a585..9aec2a5c19 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -186,17 +186,14 @@ namespace osu.Game.Screens { applyArrivingDefaults(false); - backgroundStack?.Push(ownedBackground = CreateBackground()); - - background = backgroundStack?.CurrentScreen as BackgroundScreen; - - if (background != ownedBackground) + if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true) { - // background may have not been replaced, at which point we don't want to track the background lifetime. + // If the constructed instance was not actually pushed to the background stack, we don't want to track it unnecessarily. ownedBackground?.Dispose(); ownedBackground = null; } + background = backgroundStack?.CurrentScreen as BackgroundScreen; base.OnEntering(last); } From ddaa95a1ca37e1c264850c4a239e13e83c500cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:34:18 +0900 Subject: [PATCH 045/207] Fix `pushEditor` function running twice on returning to loader --- osu.Game/Screens/Edit/EditorLoader.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 92420fa7c0..8798a7964b 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -34,10 +34,13 @@ namespace osu.Game.Screens.Edit { base.LogoArriving(logo, resuming); - // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. - // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current - // before enqueueing this screen's LogoArriving onto the logo animation sequence. - pushEditor(); + if (!resuming) + { + // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. + // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current + // before enqueueing this screen's LogoArriving onto the logo animation sequence. + pushEditor(); + } } private void pushEditor() From 7921ad451678925d6bf51628cc6015ac0c69c26b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:34:47 +0900 Subject: [PATCH 046/207] Add loading spinner in case load takes longer than expected --- osu.Game/Screens/Edit/EditorLoader.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 8798a7964b..499baeec4f 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Menu; namespace osu.Game.Screens.Edit @@ -43,10 +44,16 @@ namespace osu.Game.Screens.Edit } } - private void pushEditor() + [BackgroundDependencyLoader] + private void load() { - this.Push(new Editor(this)); - ValidForResume = false; + AddRangeInternal(new Drawable[] + { + new LoadingSpinner(true) + { + State = { Value = Visibility.Visible }, + } + }); } public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) @@ -62,6 +69,12 @@ namespace osu.Game.Screens.Edit }); } + private void pushEditor() + { + this.Push(new Editor(this)); + ValidForResume = false; + } + public void CancelPendingDifficultySwitch() { scheduledDifficultySwitch?.Cancel(); From 9edd010b1d0a52dd233ea41f2e42ea7fb05b808e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:34:54 +0900 Subject: [PATCH 047/207] Fix unnecessary background screen transition --- osu.Game/Screens/Edit/EditorLoader.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 499baeec4f..aec7d32939 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -3,11 +3,14 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit { @@ -15,7 +18,7 @@ namespace osu.Game.Screens.Edit /// Transition screen for the editor. /// Used to avoid backing out to main menu/song select when switching difficulties from within the editor. /// - public class EditorLoader : OsuScreen + public class EditorLoader : ScreenWithBeatmapBackground { public override float BackgroundParallaxAmount => 0.1f; @@ -62,9 +65,16 @@ namespace osu.Game.Screens.Edit ValidForResume = true; this.MakeCurrent(); + scheduledDifficultySwitch = Schedule(() => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + + // This screen is a weird exception to the rule that nothing after song select changes the global beatmap. + // Because of this, we need to update the background stack's beatmap to match. + // If we don't do this, the editor will see a discrepancy and create a new background, along with an unnecessary transition. + ApplyToBackground(b => b.Beatmap = Beatmap.Value); + pushEditor(); }); } From 4658577b1d272aceba1624b0954ffff11e2cb01f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 15:18:59 +0900 Subject: [PATCH 048/207] Factor in total score calculation time in results screen load --- .../Playlists/PlaylistsResultsScreen.cs | 32 ++++++++++++------- osu.Game/Screens/Ranking/ResultsScreen.cs | 14 ++++---- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index 2b252f9db7..89bc659f63 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private ScoreManager scoreManager { get; set; } + public PlaylistsResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true) : base(score, allowRetry, allowWatchingReplay) { @@ -166,23 +169,28 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// An optional pivot around which the scores were retrieved. private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) { - var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); + var scoreInfos = scores.Select(s => s.CreateScoreInfo(playlistItem)).ToArray(); - // Select a score if we don't already have one selected. - // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). - if (SelectedScore.Value == null) + // Score panels calculate total score before displaying, which can take some time. In order to count that calculation as part of the loading spinner display duration, + // calculate the total scores locally before invoking the success callback. + scoreManager.OrderByTotalScoreAsync(scoreInfos).ContinueWith(_ => Schedule(() => { - Schedule(() => + // Select a score if we don't already have one selected. + // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). + if (SelectedScore.Value == null) { - // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); - }); - } + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + }); + } - // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); + // Invoke callback to add the scores. Exclude the user's current score which was added previously. + callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); - hideLoadingSpinners(pivot); + hideLoadingSpinners(pivot); + })); } private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index d44d1f2cc9..822ee1cf90 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -52,8 +52,7 @@ namespace osu.Game.Screens.Ranking private Drawable bottomPanel; private Container detachedPanelContainer; - private bool fetchedInitialScores; - private APIRequest nextPageRequest; + private bool lastFetchCompleted; private readonly bool allowRetry; private readonly bool allowWatchingReplay; @@ -191,8 +190,10 @@ namespace osu.Game.Screens.Ranking { base.Update(); - if (fetchedInitialScores && nextPageRequest == null) + if (lastFetchCompleted) { + APIRequest nextPageRequest = null; + if (ScorePanelList.IsScrolledToStart) nextPageRequest = FetchNextPage(-1, fetchScoresCallback); else if (ScorePanelList.IsScrolledToEnd) @@ -200,10 +201,7 @@ namespace osu.Game.Screens.Ranking if (nextPageRequest != null) { - // Scheduled after children to give the list a chance to update its scroll position and not potentially trigger a second request too early. - nextPageRequest.Success += () => ScheduleAfterChildren(() => nextPageRequest = null); - nextPageRequest.Failure += _ => ScheduleAfterChildren(() => nextPageRequest = null); - + lastFetchCompleted = false; api.Queue(nextPageRequest); } } @@ -229,7 +227,7 @@ namespace osu.Game.Screens.Ranking foreach (var s in scores) addScore(s); - fetchedInitialScores = true; + lastFetchCompleted = true; }); public override void OnEntering(IScreen last) From f3d2d93aa14227f41b6f92e250de9aff5d484164 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 16:09:22 +0900 Subject: [PATCH 049/207] Remove stray newline --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 34a9610804..631455b727 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -201,7 +201,6 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestFetchScoresAfterShowingStatistics() - { DelayedFetchResultsScreen screen = null; From b6c80f04b07b33ec6a3504a6ae968c32d724dc75 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 7 Sep 2021 16:44:45 +0900 Subject: [PATCH 050/207] Add "featured artists" filter to beatmap search --- .../UserInterface/TestSceneBeatmapListingSearchControl.cs | 3 ++- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 ++- osu.Game/Overlays/BeatmapListing/SearchGeneral.cs | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index abd1baf0ac..008d91f649 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using Humanizer; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -73,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); - control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); + control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true); control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 8ce495e274..ae082ca82e 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using Humanizer; using JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; @@ -83,7 +84,7 @@ namespace osu.Game.Online.API.Requests req.AddParameter("q", query); if (General != null && General.Any()) - req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().ToLowerInvariant()))); + req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().Underscore()))); if (ruleset.ID.HasValue) req.AddParameter("m", ruleset.ID.Value.ToString()); diff --git a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs index d334b82e88..9387020bdf 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs @@ -19,6 +19,10 @@ namespace osu.Game.Overlays.BeatmapListing [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.GeneralFollows))] [Description("Subscribed mappers")] - Follows + Follows, + + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.GeneralFeaturedArtists))] + [Description("Featured artists")] + FeaturedArtists } } From d922210d2feda6f03de5717d3ca53e99b4864feb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 16:46:27 +0900 Subject: [PATCH 051/207] Fix `TestSceneDeleteLocalScore` not properly comparing post-delete scores --- .../TestSceneDeleteLocalScore.cs | 19 +++++++------ .../Online/Leaderboards/LeaderboardScore.cs | 27 ++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 8efee2723f..2e30ed9827 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapManager beatmapManager; private ScoreManager scoreManager; - private readonly List scores = new List(); + private readonly List importedScores = new List(); private BeatmapInfo beatmap; [Cached] @@ -100,11 +100,9 @@ namespace osu.Game.Tests.Visual.UserInterface User = new User { Username = "TestUser" }, }; - scores.Add(scoreManager.Import(score).Result); + importedScores.Add(scoreManager.Import(score).Result); } - scores.Sort(Comparer.Create((s1, s2) => s2.TotalScore.CompareTo(s1.TotalScore))); - return dependencies; } @@ -134,9 +132,14 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDeleteViaRightClick() { + ScoreInfo scoreBeingDeleted = null; AddStep("open menu for top score", () => { - InputManager.MoveMouseTo(leaderboard.ChildrenOfType().First()); + var leaderboardScore = leaderboard.ChildrenOfType().First(); + + scoreBeingDeleted = leaderboardScore.Score; + + InputManager.MoveMouseTo(leaderboardScore); InputManager.Click(MouseButton.Right); }); @@ -158,14 +161,14 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s != scores[0])); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID)); } [Test] public void TestDeleteViaDatabase() { - AddStep("delete top score", () => scoreManager.Delete(scores[0])); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s != scores[0])); + AddStep("delete top score", () => scoreManager.Delete(importedScores[0])); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID)); } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 934b905a1a..090a4014aa 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -34,6 +34,8 @@ namespace osu.Game.Online.Leaderboards { public const float HEIGHT = 60; + public readonly ScoreInfo Score; + private const float corner_radius = 5; private const float edge_margin = 5; private const float background_alpha = 0.25f; @@ -41,7 +43,6 @@ namespace osu.Game.Online.Leaderboards protected Container RankContainer { get; private set; } - private readonly ScoreInfo score; private readonly int? rank; private readonly bool allowHighlight; @@ -67,7 +68,7 @@ namespace osu.Game.Online.Leaderboards public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) { - this.score = score; + this.Score = score; this.rank = rank; this.allowHighlight = allowHighlight; @@ -78,9 +79,9 @@ namespace osu.Game.Online.Leaderboards [BackgroundDependencyLoader] private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager) { - var user = score.User; + var user = Score.User; - statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList(); + statisticsLabels = GetStatistics(Score).Select(s => new ScoreComponentLabel(s)).ToList(); ClickableAvatar innerAvatar; @@ -198,7 +199,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Current = scoreManager.GetBindableTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(Score), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container @@ -206,7 +207,7 @@ namespace osu.Game.Online.Leaderboards Size = new Vector2(40f, 20f), Children = new[] { - scoreRank = new UpdateableRank(score.Rank) + scoreRank = new UpdateableRank(Score.Rank) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -223,7 +224,7 @@ namespace osu.Game.Online.Leaderboards AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(1), - ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) + ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) }, }, }, @@ -389,14 +390,14 @@ namespace osu.Game.Online.Leaderboards { List items = new List(); - if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods)); + if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods)); - if (score.Files?.Count > 0) - items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(score))); + if (Score.Files?.Count > 0) + items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(Score))); - if (score.ID != 0) - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); + if (Score.ID != 0) + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); return items.ToArray(); } From a42527b8f553f83923647259c53f9ce53bf67392 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 16:46:40 +0900 Subject: [PATCH 052/207] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 05367c00f6..7378450c38 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ae423bac8c..d80dd075ee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index be737392e1..8ce757974e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 91a48084c7b1e71b83f552db216e11f2d1a04143 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 17:25:03 +0900 Subject: [PATCH 053/207] Update asserts in line with framework changes to `PlaybackPosition` --- osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index e45b8f7dc5..785f31386d 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -40,10 +40,10 @@ namespace osu.Game.Tests.NonVisual.Skinning assertPlaybackPosition(0); AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime.Value = 1000); - assertPlaybackPosition(-1000); + assertPlaybackPosition(0); AddStep("set current time to 500", () => animationTimeReference.ManualClock.CurrentTime = 500); - assertPlaybackPosition(-500); + assertPlaybackPosition(0); } private void assertPlaybackPosition(double expectedPosition) From 5ab2f4b38665b0971fff93d752b7213b176934b4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 7 Sep 2021 17:31:54 +0900 Subject: [PATCH 054/207] Give an orange colour to "featured artists" filter to match web --- .../BeatmapListingSearchControl.cs | 2 +- .../BeatmapSearchGeneralFilterRow.cs | 39 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 4 +- 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 0626f236b8..d1e2ac38df 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -127,7 +127,7 @@ namespace osu.Game.Overlays.BeatmapListing Padding = new MarginPadding { Horizontal = 10 }, Children = new Drawable[] { - generalFilter = new BeatmapSearchMultipleSelectionFilterRow(BeatmapsStrings.ListingSearchFiltersGeneral), + generalFilter = new BeatmapSearchGeneralFilterRow(), modeFilter = new BeatmapSearchRulesetFilterRow(), categoryFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersStatus), genreFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersGenre), diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs new file mode 100644 index 0000000000..fb9e1c0420 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -0,0 +1,39 @@ +// 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.Resources.Localisation.Web; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchGeneralFilterRow : BeatmapSearchMultipleSelectionFilterRow + { + public BeatmapSearchGeneralFilterRow() + : base(BeatmapsStrings.ListingSearchFiltersGeneral) + { + } + + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new GeneralFilter(); + + private class GeneralFilter : MultipleSelectionFilter + { + protected override MultipleSelectionFilterTabItem CreateTabItem(SearchGeneral value) + { + if (value == SearchGeneral.FeaturedArtists) + return new FeaturedArtistsTabItem(); + + return new MultipleSelectionFilterTabItem(value); + } + } + + private class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem + { + public FeaturedArtistsTabItem() + : base(SearchGeneral.FeaturedArtists) + { + } + + protected override Color4 GetStateColour() => OverlayColourProvider.Orange.Colour1; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 46cb1e822f..9274cf20aa 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -71,10 +71,10 @@ namespace osu.Game.Overlays.BeatmapListing private void updateState() { - text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint); + text.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); } - private Color4 getStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; + protected virtual Color4 GetStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; } } From 92f59c10f5108d23d84698d0b5c978726c629b5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 17:45:21 +0900 Subject: [PATCH 055/207] Remove redundant `this.` in assignment --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 090a4014aa..26749a23f9 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -68,7 +68,8 @@ namespace osu.Game.Online.Leaderboards public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) { - this.Score = score; + Score = score; + this.rank = rank; this.allowHighlight = allowHighlight; From 3d8faea4b073718900da16d0a0b522fb700aed9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 18:52:25 +0900 Subject: [PATCH 056/207] Simplify nesting of `OrderByTotalScoreAsync` --- osu.Game/Scoring/ScoreManager.cs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 2cda8959f4..81e701f001 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -116,25 +116,20 @@ namespace osu.Game.Scoring { var difficultyCache = difficulties?.Invoke(); - if (difficultyCache == null) - return orderByTotalScore(scores); - - // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. - foreach (var s in scores) + if (difficultyCache != null) { - await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. + foreach (var s in scores) + { + await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } } - return orderByTotalScore(scores); - - ScoreInfo[] orderByTotalScore(IEnumerable incoming) - { - // We're calling .Result, but this should not be a blocking call due to the above GetDifficultyAsync() calls. - return incoming.OrderByDescending(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken).Result) - .ThenBy(s => s.OnlineScoreID) - .ToArray(); - } + // We're calling .Result, but this should not be a blocking call due to the above GetDifficultyAsync() calls. + return scores.OrderByDescending(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken).Result) + .ThenBy(s => s.OnlineScoreID) + .ToArray(); } /// From 2f42a8461e64ac7f33b75fcb2171b61bc296c99c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 20:19:07 +0900 Subject: [PATCH 057/207] Fix diffcalc workflow triggering too often --- .github/workflows/test-diffcalc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 7728d91152..4274d01bab 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -23,9 +23,9 @@ jobs: continue-on-error: true if: | - ${{ github.event.issue.pull_request }} && + github.event.issue.pull_request && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} + (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') strategy: fail-fast: false From f54d5675db79ae378f8623115e984e65446198c3 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Tue, 7 Sep 2021 17:06:12 +0100 Subject: [PATCH 058/207] check if user joined requested channel already --- osu.Game/Online/Chat/ChannelManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index d2500f0d7f..43da69db97 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -264,6 +264,11 @@ namespace osu.Game.Online.Chat break; } + // Check if the user has joined requested channel already. + var alreadyJoinedChannel = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name == content); + if (alreadyJoinedChannel != null) + CurrentChannel.Value = alreadyJoinedChannel; + var request = new GetUserRequest(content); request.Success += OpenPrivateChannel; request.Failure += _ => target.AddNewMessages(new ErrorMessage("User not found.")); From be9540e53507c83cadd5fc6cdaf18bf5f2495e02 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Tue, 7 Sep 2021 17:17:10 +0100 Subject: [PATCH 059/207] fix "key" having wrong url parameter name --- osu.Game/Online/API/Requests/GetUserRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index e49c4ab298..fe954372bf 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -43,7 +43,7 @@ namespace osu.Game.Online.API.Requests Ruleset = ruleset; } - protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?k={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; private enum LookupType { From b1c89f761871445a01997c7ac62ba6919eb03055 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Tue, 7 Sep 2021 17:22:59 +0100 Subject: [PATCH 060/207] ignore case when search for already joined channel --- osu.Game/Online/Chat/ChannelManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 43da69db97..05f9d244f2 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -265,7 +265,8 @@ namespace osu.Game.Online.Chat } // Check if the user has joined requested channel already. - var alreadyJoinedChannel = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name == content); + var alreadyJoinedChannel = JoinedChannels.FirstOrDefault( + c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase)); if (alreadyJoinedChannel != null) CurrentChannel.Value = alreadyJoinedChannel; From 255f8a9769a0fc1c6ad99df1f5e9a37b2c305bf1 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Tue, 7 Sep 2021 17:25:47 +0100 Subject: [PATCH 061/207] add alias "/msg" (also a command in stable) --- osu.Game/Online/Chat/ChannelManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 05f9d244f2..34c6d048a3 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -257,6 +257,7 @@ namespace osu.Game.Online.Chat break; case "chat": + case "msg": case "query": if (string.IsNullOrWhiteSpace(content)) { From 2097889ce1a83a8ed2415c7fd81e7d990e3ade1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Sep 2021 20:58:14 +0200 Subject: [PATCH 062/207] Add failing test case --- .../Visual/Ranking/TestSceneScorePanelList.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index b2585c0509..6f3b3028be 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -203,6 +203,71 @@ namespace osu.Game.Tests.Visual.Ranking assertExpandedPanelCentred(); } + [Test] + public void TestKeyboardNavigation() + { + var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 }; + var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 }; + var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 }; + + createListStep(() => new ScorePanelList()); + + AddStep("add scores and select middle", () => + { + // order of addition purposefully scrambled. + list.AddScore(middleScore); + list.AddScore(lowestScore); + list.AddScore(highestScore); + list.SelectedScore.Value = middleScore; + }); + + assertScoreState(highestScore, false); + assertScoreState(middleScore, true); + assertScoreState(lowestScore, false); + + AddStep("press left", () => InputManager.Key(Key.Left)); + + assertScoreState(highestScore, true); + assertScoreState(middleScore, false); + assertScoreState(lowestScore, false); + assertExpandedPanelCentred(); + + AddStep("press left at start of list", () => InputManager.Key(Key.Left)); + + assertScoreState(highestScore, true); + assertScoreState(middleScore, false); + assertScoreState(lowestScore, false); + assertExpandedPanelCentred(); + + AddStep("press right", () => InputManager.Key(Key.Right)); + + assertScoreState(highestScore, false); + assertScoreState(middleScore, true); + assertScoreState(lowestScore, false); + assertExpandedPanelCentred(); + + AddStep("press right again", () => InputManager.Key(Key.Right)); + + assertScoreState(highestScore, false); + assertScoreState(middleScore, false); + assertScoreState(lowestScore, true); + assertExpandedPanelCentred(); + + AddStep("press right at end of list", () => InputManager.Key(Key.Right)); + + assertScoreState(highestScore, false); + assertScoreState(middleScore, false); + assertScoreState(lowestScore, true); + assertExpandedPanelCentred(); + + AddStep("press left", () => InputManager.Key(Key.Left)); + + assertScoreState(highestScore, false); + assertScoreState(middleScore, true); + assertScoreState(lowestScore, false); + assertExpandedPanelCentred(); + } + private void createListStep(Func creationFunc) { AddStep("create list", () => Child = list = creationFunc().With(d => From 8cc444df5f6a17fbea64c5d2b34d618fbd04530e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Sep 2021 21:14:38 +0200 Subject: [PATCH 063/207] Fix incorrect keyboard navigation order in score panel list --- osu.Game/Screens/Ranking/ScorePanelList.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index a847df344f..d5b8a4c8ea 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -306,18 +307,18 @@ namespace osu.Game.Screens.Ranking if (expandedPanel == null) return base.OnKeyDown(e); - var expandedPanelIndex = flow.GetPanelIndex(expandedPanel.Score); - switch (e.Key) { case Key.Left: - if (expandedPanelIndex > 0) - SelectedScore.Value = flow.Children[expandedPanelIndex - 1].Panel.Score; + var previousScore = flow.GetPreviousScore(expandedPanel.Score); + if (previousScore != null) + SelectedScore.Value = previousScore; return true; case Key.Right: - if (expandedPanelIndex < flow.Count - 1) - SelectedScore.Value = flow.Children[expandedPanelIndex + 1].Panel.Score; + var nextScore = flow.GetNextScore(expandedPanel.Score); + if (nextScore != null) + SelectedScore.Value = nextScore; return true; } @@ -336,6 +337,12 @@ namespace osu.Game.Screens.Ranking public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); + [CanBeNull] + public ScoreInfo GetPreviousScore(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).LastOrDefault()?.Panel.Score; + + [CanBeNull] + public ScoreInfo GetNextScore(ScoreInfo score) => applySorting(Children).SkipWhile(s => s.Panel.Score != score).ElementAtOrDefault(1)?.Panel.Score; + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineScoreID); From d8fe98fe12ac5e305c00c78657322b50c337cfa6 Mon Sep 17 00:00:00 2001 From: Ethan Ng Date: Tue, 7 Sep 2021 14:06:23 -0600 Subject: [PATCH 064/207] Fixed grammatical error in ChangelogSupporterPromo --- osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index 508c8399b6..689a7bb4a3 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Changelog }; supportLinkText.AddText("Support further development of osu! and "); - supportLinkText.AddLink("become and osu!supporter", "https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold)); + supportLinkText.AddLink("become an osu!supporter", "https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold)); supportLinkText.AddText(" today!"); imageContainer.Children = new Drawable[] From b8a1ebb786c6bd8a9581d62f489ccd598563e513 Mon Sep 17 00:00:00 2001 From: sh0ckR6 Date: Tue, 7 Sep 2021 16:54:21 -0400 Subject: [PATCH 065/207] Hide Popover after failed password attempt Instead of throwing an error, just close the popover and let the user continue --- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 3 ++- .../Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 11 +++++++---- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 6 ++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 3b6c1c8be0..4b3617c74a 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -87,7 +87,8 @@ namespace osu.Game.Screens.OnlinePlay.Components currentJoinRoomRequest.Failure += exception => { - if (!(exception is OperationCanceledException)) + // provide error output if the operation wasn't canceled and the error doesn't stem from an invalid password + if (!(exception is OperationCanceledException) && !((APIException)exception).Message.Equals("Invalid room password", StringComparison.Ordinal)) Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); onError?.Invoke(exception.ToString()); }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 6dc3cd18c5..c8b647fc9a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -178,7 +178,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { private readonly Room room; - public Action JoinRequested; + public Action, Action> JoinRequested; public PasswordEntryPopover(Room room) { @@ -186,6 +186,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge } private OsuPasswordTextBox passwordTextbox; + private TriangleButton joinButton; [BackgroundDependencyLoader] private void load() @@ -201,15 +202,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge passwordTextbox = new OsuPasswordTextBox { Width = 200, + PlaceholderText = "password", }, - new TriangleButton + joinButton = new TriangleButton { Width = 80, Text = "Join Room", - Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text) } } }; + + joinButton.Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text, null, _ => this.HidePopover()); } protected override void LoadComplete() @@ -217,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); - passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text); + passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text, null, _ => this.HidePopover()); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index cca1394b6d..08bdd0487a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -290,7 +290,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge popoverContainer.HidePopover(); } - public void Join(Room room, string password) => Schedule(() => + public void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) => Schedule(() => { if (joiningRoomOperation != null) return; @@ -302,10 +302,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge Open(room); joiningRoomOperation?.Dispose(); joiningRoomOperation = null; - }, _ => + onSuccess?.Invoke(room); + }, error => { joiningRoomOperation?.Dispose(); joiningRoomOperation = null; + onFailure?.Invoke(error); }); }); From b1f91596a7e7a07082056686ba49a8acb81244b2 Mon Sep 17 00:00:00 2001 From: sh0ckR6 Date: Tue, 7 Sep 2021 20:05:24 -0400 Subject: [PATCH 066/207] Give user feedback on password attempt fail Shake the popover Set the input box's color to red and set the placeholder text to "incorrect password" --- .../Graphics/Containers/ShakeContainer.cs | 7 +++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 47 +++++++++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs index dca9df1e98..ffa8ef585e 100644 --- a/osu.Game/Graphics/Containers/ShakeContainer.cs +++ b/osu.Game/Graphics/Containers/ShakeContainer.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.Graphics; using osu.Framework.Graphics.Containers; @@ -26,6 +27,11 @@ namespace osu.Game.Graphics.Containers /// public float ShakeMagnitude = 8; + /// + /// Fired when finishes + /// + public event Action OnShakeFinish; + /// /// Shake the contents of this container. /// @@ -50,6 +56,7 @@ namespace osu.Game.Graphics.Containers } sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine); + sequence.Finally(_ => OnShakeFinish?.Invoke()); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index c8b647fc9a..bedee39a26 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; @@ -187,32 +188,50 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private OsuPasswordTextBox passwordTextbox; private TriangleButton joinButton; + private ShakeContainer shakeContainer; [BackgroundDependencyLoader] private void load() { - Child = new FillFlowContainer + shakeContainer = new ShakeContainer { Margin = new MarginPadding(10), - Spacing = new Vector2(5), AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] + Child = new FillFlowContainer { - passwordTextbox = new OsuPasswordTextBox + Margin = new MarginPadding(10), + Spacing = new Vector2(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] { - Width = 200, - PlaceholderText = "password", - }, - joinButton = new TriangleButton - { - Width = 80, - Text = "Join Room", + passwordTextbox = new OsuPasswordTextBox + { + Width = 200, + PlaceholderText = "password", + }, + joinButton = new TriangleButton + { + Width = 80, + Text = "Join Room", + } } } }; + Child = shakeContainer; - joinButton.Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text, null, _ => this.HidePopover()); + joinButton.Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text, null, joinFailed); + } + + private void joinFailed(string error) + { + passwordTextbox.Text = string.Empty; + passwordTextbox.PlaceholderText = "incorrect password"; + passwordTextbox.Colour = Color4.Red; + + shakeContainer.OnShakeFinish += () => passwordTextbox.Colour = Color4.White; + + shakeContainer.Shake(); } protected override void LoadComplete() @@ -220,7 +239,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); - passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text, null, _ => this.HidePopover()); + passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text, null, joinFailed); } } } From 7543f9dfb0155a3e6232f429add9f04bb7b43231 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 8 Sep 2021 12:21:24 +0900 Subject: [PATCH 067/207] Add featured artist track ID online info --- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 6 ++++++ osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 48f1f0ce68..bf8063cfaf 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -90,6 +90,12 @@ namespace osu.Game.Beatmaps /// The song language of this beatmap set. /// public BeatmapSetOnlineLanguage Language { get; set; } + + /// + /// The song ID of this beatmap set. + /// Non-null only if the song is from a featured artist. + /// + public int? TrackId { get; set; } } public class BeatmapSetOnlineGenre diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 45d9c9405f..f653a654ca 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"ratings")] private int[] ratings { get; set; } + [JsonProperty(@"track_id")] + private int? trackId { get; set; } + [JsonProperty(@"user_id")] private int creatorId { @@ -106,7 +109,8 @@ namespace osu.Game.Online.API.Requests.Responses Availability = availability, HasFavourited = hasFavourited, Genre = genre, - Language = language + Language = language, + TrackId = trackId }, }; From 81c9d831f496702d2536b7fb27c04b96a9f3a24f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 13:26:07 +0900 Subject: [PATCH 068/207] Cache reflection portion of `SettingSource` lookups Fixes the mod-related overhead portion of https://github.com/ppy/osu/issues/14638. There's still a significant performance issue that will be addressed separately. --- .../Configuration/SettingSourceAttribute.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index f373e59417..4fff81b587 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -167,18 +167,7 @@ namespace osu.Game.Configuration } } - public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) - { - foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) - { - var attr = property.GetCustomAttribute(true); - - if (attr == null) - continue; - - yield return (attr, property); - } - } + public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this T obj) => SettingSourceCache.SettingSourceProperties; public static ICollection<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj) => obj.GetSettingsSourceProperties() @@ -196,5 +185,14 @@ namespace osu.Game.Configuration protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 100); } } + + private static class SettingSourceCache + { + public static (SettingSourceAttribute, PropertyInfo)[] SettingSourceProperties { get; } = + typeof(T).GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance) + .Select(property => (property.GetCustomAttribute(), property)) + .Where(pair => pair.Item1 != null) + .ToArray(); + } } } From f96be2cab89cd11e08325e4e22f1a8a45a6ea308 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 8 Sep 2021 13:27:20 +0900 Subject: [PATCH 069/207] Add featured artist marker to beatmap set listing and overlay --- .../BeatmapListing/Panels/GridBeatmapPanel.cs | 28 +++++++++++--- .../BeatmapListing/Panels/ListBeatmapPanel.cs | 26 +++++++++++-- .../BeatmapSet/BeatmapSetBadgePill.cs | 32 ++++++++++++++++ .../BeatmapSet/BeatmapSetHeaderContent.cs | 23 +++++++++-- .../BeatmapSet/ExplicitContentBeatmapPill.cs | 38 +++++-------------- .../BeatmapSet/FeaturedArtistBeatmapPill.cs | 27 +++++++++++++ 6 files changed, 133 insertions(+), 41 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePill.cs create mode 100644 osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index 4d5c387c4a..c078127353 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels private const float horizontal_padding = 10; private const float vertical_padding = 5; - private FillFlowContainer bottomPanel, statusContainer, titleContainer; + private FillFlowContainer bottomPanel, statusContainer, titleContainer, artistContainer; private PlayButton playButton; private Box progressBar; @@ -89,11 +89,19 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }, } }, - new OsuSpriteText + artistContainer = new FillFlowContainer { - Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), - Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) - }, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) + } + } + } }, }, new Container @@ -213,6 +221,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } + if (SetInfo.OnlineInfo?.TrackId != null) + { + artistContainer.Add(new FeaturedArtistBeatmapPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 10f, Top = 2f }, + }); + } + if (SetInfo.OnlineInfo?.HasVideo ?? false) { statusContainer.Add(new IconPill(FontAwesome.Solid.Film)); diff --git a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs index 00ffd168c1..5011749c5f 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels private const float vertical_padding = 5; private const float height = 70; - private FillFlowContainer statusContainer, titleContainer; + private FillFlowContainer statusContainer, titleContainer, artistContainer; protected BeatmapPanelDownloadButton DownloadButton; private PlayButton playButton; private Box progressBar; @@ -112,10 +112,18 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }, } }, - new OsuSpriteText + artistContainer = new FillFlowContainer { - Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), - Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) + }, + }, }, } }, @@ -227,6 +235,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } + if (SetInfo.OnlineInfo?.TrackId != null) + { + artistContainer.Add(new FeaturedArtistBeatmapPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 10f, Top = 2f }, + }); + } + if (SetInfo.OnlineInfo?.HasVideo ?? false) { statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePill.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePill.cs new file mode 100644 index 0000000000..15c691103a --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePill.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapSetBadgePill : CircularContainer + { + public BeatmapSetBadgePill() + { + AutoSizeAxes = Axes.Both; + Masking = true; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OsuColour colours, [CanBeNull] OverlayColourProvider colourProvider) + { + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider?.Background5 ?? colours?.Gray2 ?? Color4.DarkGray, + }); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index a61640a02e..c3b6444a24 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -37,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapSet private readonly OsuSpriteText title, artist; private readonly AuthorInfo author; private readonly ExplicitContentBeatmapPill explicitContentPill; + private readonly FeaturedArtistBeatmapPill featuredArtistPill; private readonly FillFlowContainer downloadButtonsContainer; private readonly BeatmapAvailability beatmapAvailability; private readonly BeatmapSetOnlineStatusPill onlineStatusPill; @@ -129,10 +130,25 @@ namespace osu.Game.Overlays.BeatmapSet } } }, - artist = new OsuSpriteText + new FillFlowContainer { - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), - Margin = new MarginPadding { Bottom = 20 } + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 20 }, + Children = new Drawable[] + { + artist = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), + }, + featuredArtistPill = new FeaturedArtistBeatmapPill + { + Alpha = 0f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 10 } + } + } }, new Container { @@ -233,6 +249,7 @@ namespace osu.Game.Overlays.BeatmapSet artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist); explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0; + featuredArtistPill.Alpha = setInfo.NewValue.OnlineInfo.TrackId != null ? 1 : 0; onlineStatusPill.FadeIn(500, Easing.OutQuint); onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status; diff --git a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs index ba78592ed2..60fa3ea900 100644 --- a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs @@ -4,44 +4,24 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet { - public class ExplicitContentBeatmapPill : CompositeDrawable + public class ExplicitContentBeatmapPill : BeatmapSetBadgePill { - public ExplicitContentBeatmapPill() + [BackgroundDependencyLoader] + private void load() { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, OverlayColourProvider colourProvider) - { - InternalChild = new CircularContainer + Add(new OsuSpriteText { - Masking = true, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider?.Background5 ?? colours.Gray2, - }, - new OsuSpriteText - { - Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, - Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), - Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Orange.Colour2, - } - } - }; + Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, + Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Colour = OverlayColourProvider.Orange.Colour2, + }); } } } diff --git a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs new file mode 100644 index 0000000000..7facc2953a --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class FeaturedArtistBeatmapPill : BeatmapSetBadgePill + { + [BackgroundDependencyLoader] + private void load() + { + Add(new OsuSpriteText + { + Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, + Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Colour = OverlayColourProvider.Blue.Colour1 + }); + } + } +} From 8745d299dc36f4817e73729d5ead419689397046 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 8 Sep 2021 13:27:45 +0900 Subject: [PATCH 070/207] Add visual tests for featured artist markers --- .../Online/TestSceneBeatmapSetOverlay.cs | 11 +++++++++++ .../Visual/Online/TestSceneDirectPanel.cs | 19 ++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index edc1696456..de7e4075f3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -246,6 +246,17 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestFeaturedBeatmap() + { + AddStep("show featured map", () => + { + var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + beatmapSet.OnlineInfo.TrackId = 1; + overlay.ShowBeatmapSet(beatmapSet); + }); + } + [Test] public void TestHide() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index fd5f306e07..722010ace2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -99,16 +99,23 @@ namespace osu.Game.Tests.Visual.Online [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - var normal = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var normal = getBeatmapSet(); normal.OnlineInfo.HasVideo = true; normal.OnlineInfo.HasStoryboard = true; var undownloadable = getUndownloadableBeatmapSet(); var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets); - var explicitMap = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var explicitMap = getBeatmapSet(); explicitMap.OnlineInfo.HasExplicitContent = true; + var featuredMap = getBeatmapSet(); + featuredMap.OnlineInfo.TrackId = 1; + + var explicitFeaturedMap = getBeatmapSet(); + explicitFeaturedMap.OnlineInfo.HasExplicitContent = true; + explicitFeaturedMap.OnlineInfo.TrackId = 2; + Child = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, @@ -125,13 +132,19 @@ namespace osu.Game.Tests.Visual.Online new GridBeatmapPanel(undownloadable), new GridBeatmapPanel(manyDifficulties), new GridBeatmapPanel(explicitMap), + new GridBeatmapPanel(featuredMap), + new GridBeatmapPanel(explicitFeaturedMap), new ListBeatmapPanel(normal), new ListBeatmapPanel(undownloadable), new ListBeatmapPanel(manyDifficulties), - new ListBeatmapPanel(explicitMap) + new ListBeatmapPanel(explicitMap), + new ListBeatmapPanel(featuredMap), + new ListBeatmapPanel(explicitFeaturedMap) }, }, }; + + BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; } } } From 217ca754aef221d955d25203d7aca757594ed184 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 8 Sep 2021 13:45:05 +0900 Subject: [PATCH 071/207] Add sound for team swaps --- .../Multiplayer/Participants/TeamDisplay.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 351b9b3673..915cf30963 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -3,6 +3,8 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -22,6 +24,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants private Drawable box; + private Sample sampleTeamSwap; + [Resolved] private OsuColour colours { get; set; } @@ -39,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { box = new Container { @@ -72,6 +76,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { InternalChild = box; } + + sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap"); } private void changeTeam() @@ -99,6 +105,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants if (newTeam == displayedTeam) return; + if (newTeam != null && displayedTeam != null) + sampleTeamSwap?.Play(); + displayedTeam = newTeam; if (displayedTeam != null) From 9637326f0c8be64912660890f59f3175a68b908a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 8 Sep 2021 15:50:19 +0900 Subject: [PATCH 072/207] Allow customizing link style by override in `LinkFlowContainer` --- .../Graphics/Containers/LinkFlowContainer.cs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 85ef779e48..21c1d70d45 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -87,23 +87,25 @@ namespace osu.Game.Graphics.Containers private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null) { - AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) + var linkCompiler = CreateLinkCompiler(drawables.OfType()); + linkCompiler.RelativeSizeAxes = Axes.Both; + linkCompiler.TooltipText = tooltipText; + linkCompiler.Action = () => { - RelativeSizeAxes = Axes.Both, - TooltipText = tooltipText, - Action = () => - { - if (action != null) - action(); - else if (game != null) - game.HandleLink(link); - // fallback to handle cases where OsuGame is not available, ie. tournament client. - else if (link.Action == LinkAction.External) - host.OpenUrlExternally(link.Argument); - }, - }); + if (action != null) + action(); + else if (game != null) + game.HandleLink(link); + // fallback to handle cases where OsuGame is not available, ie. tournament client. + else if (link.Action == LinkAction.External) + host.OpenUrlExternally(link.Argument); + }; + + AddInternal(linkCompiler); } + protected virtual DrawableLinkCompiler CreateLinkCompiler(IEnumerable parts) => new DrawableLinkCompiler(parts); + // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow. From d417f03a39d22f7e5b39a31735659fb531f354be Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 8 Sep 2021 15:52:01 +0900 Subject: [PATCH 073/207] Simplify link style customization code --- .../Overlays/Changelog/ChangelogSupporterPromo.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index 689a7bb4a3..c2b855a0f8 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Changelog }; supportLinkText.AddText("Support further development of osu! and "); - supportLinkText.AddLink("become an osu!supporter", "https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold)); + supportLinkText.AddLink("become an osu!supporter", @"https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold)); supportLinkText.AddText(" today!"); imageContainer.Children = new Drawable[] @@ -170,27 +170,18 @@ namespace osu.Game.Overlays.Changelog { } - public new void AddLink(string text, string url, Action creationParameters) => - AddInternal(new SupporterPromoLinkCompiler(AddText(text, creationParameters)) { Url = url }); + protected override DrawableLinkCompiler CreateLinkCompiler(IEnumerable parts) => new SupporterPromoLinkCompiler(parts); private class SupporterPromoLinkCompiler : DrawableLinkCompiler { - [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } - - public string Url; - public SupporterPromoLinkCompiler(IEnumerable parts) : base(parts) { - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load(OsuColour colour) { - TooltipText = Url; - Action = () => game?.HandleLink(Url); IdleColour = colour.PinkDark; HoverColour = Color4.White; } From 037b9cfb59d189576ad6f0cd3ec8ed04964d4968 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 16:53:23 +0900 Subject: [PATCH 074/207] Load `DrawableLoungRoom`s asynchronously --- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 76cb02199b..8e3c74aba9 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -64,9 +64,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override void LoadComplete() { - rooms.CollectionChanged += roomsChanged; roomManager.RoomsUpdated += updateSorting; + rooms.CollectionChanged += roomsChanged; rooms.BindTo(roomManager.Rooms); Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true); @@ -108,10 +108,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void addRooms(IEnumerable rooms) { - foreach (var room in rooms) - roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } }); + LoadComponentsAsync(rooms.Select(room => + new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } }), rooms => + { + // check against rooms collection to ensure the room wasn't removed since this async load started. + roomFlow.AddRange(rooms.Where(r => this.rooms.Contains(r.Room))); - applyFilterCriteria(Filter?.Value); + applyFilterCriteria(Filter?.Value); + }); } private void removeRooms(IEnumerable rooms) From bebb9d7e675f18e7667ab202335efcb542a1f32b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 17:13:12 +0900 Subject: [PATCH 075/207] Wrap main content of `DrawableRoom` --- .../Lounge/Components/DrawableRoom.cs | 286 +++++++++--------- 1 file changed, 146 insertions(+), 140 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 106211c833..964a9eddfb 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -43,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private PasswordProtectedIcon passwordIcon; private EndDateInfo endDateInfo; + private DelayedLoadWrapper wrapper; + public DrawableRoom(Room room) { Room = room; @@ -75,155 +77,156 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { d.RelativeSizeAxes = Axes.Both; }), - new Container - { - Name = @"Room content", - RelativeSizeAxes = Axes.Both, - // This negative padding resolves 1px gaps between this background and the background above. - Padding = new MarginPadding { Left = 20, Vertical = -0.5f }, - Child = new Container + wrapper = new DelayedLoadWrapper(() => + new Container { + Name = @"Room content", RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = CORNER_RADIUS, - Children = new Drawable[] + // This negative padding resolves 1px gaps between this background and the background above. + Padding = new MarginPadding { Left = 20, Vertical = -0.5f }, + Child = new Container { - new GridContainer + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = CORNER_RADIUS, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new GridContainer { - new Dimension(GridSizeMode.Relative, 0.2f) - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Background5, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f)) - }, - } - } - }, - new Container - { - Name = @"Left details", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Left = 20, - Vertical = 5 - }, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Children = new Drawable[] - { - new RoomStatusPill - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }, - specialCategoryPill = new RoomSpecialCategoryPill - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }, - endDateInfo = new EndDateInfo - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 3 }, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new RoomNameText(), - new RoomHostText(), - } - } - }, + new Dimension(GridSizeMode.Relative, 0.2f) }, - new FillFlowContainer + Content = new[] { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Children = new Drawable[] + new Drawable[] { - new PlaylistCountPill + new Box { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Colour = colours.Background5, }, - new StarRatingRangeDisplay + new Box { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.8f) + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f)) + }, + } + } + }, + new Container + { + Name = @"Left details", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Left = 20, + Vertical = 5 + }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new RoomStatusPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + specialCategoryPill = new RoomSpecialCategoryPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + endDateInfo = new EndDateInfo + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 3 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new RoomNameText(), + new RoomHostText(), + } + } + }, + }, + new FillFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new PlaylistCountPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new StarRatingRangeDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.8f) + } } } } - } - }, - new FillFlowContainer - { - Name = "Right content", - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Spacing = new Vector2(5), - Padding = new MarginPadding - { - Right = 10, - Vertical = 20, }, - Children = new Drawable[] + new FillFlowContainer { - ButtonsContainer = new Container + Name = "Right content", + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Spacing = new Vector2(5), + Padding = new MarginPadding { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X + Right = 10, + Vertical = 20, }, - recentParticipantsList = new RecentParticipantsList + Children = new Drawable[] { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - NumberOfCircles = NumberOfAvatars + ButtonsContainer = new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + }, + recentParticipantsList = new RecentParticipantsList + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + NumberOfCircles = NumberOfAvatars + } } - } + }, + passwordIcon = new PasswordProtectedIcon { Alpha = 0 } }, - passwordIcon = new PasswordProtectedIcon { Alpha = 0 } }, - }, - }, + }, 0) }; } @@ -231,23 +234,26 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - roomCategory.BindTo(Room.Category); - roomCategory.BindValueChanged(c => + wrapper.DelayedLoadComplete += _ => { - if (c.NewValue == RoomCategory.Spotlight) - specialCategoryPill.Show(); - else - specialCategoryPill.Hide(); - }, true); + roomCategory.BindTo(Room.Category); + roomCategory.BindValueChanged(c => + { + if (c.NewValue == RoomCategory.Spotlight) + specialCategoryPill.Show(); + else + specialCategoryPill.Hide(); + }, true); - roomType.BindTo(Room.Type); - roomType.BindValueChanged(t => - { - endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0; - }, true); + roomType.BindTo(Room.Type); + roomType.BindValueChanged(t => + { + endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0; + }, true); - hasPassword.BindTo(Room.HasPassword); - hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); + hasPassword.BindTo(Room.HasPassword); + hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); + }; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 3e41d8b32e146285b610b93b9f2f6e2d5e69221c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 17:30:13 +0900 Subject: [PATCH 076/207] Reduce initial fade transforms --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 6dc3cd18c5..a13d67a0c9 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -83,12 +83,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.LoadComplete(); - if (matchingFilter) - this.FadeInFromZero(transition_duration); - else - Alpha = 0; + Alpha = matchingFilter ? 1 : 0; + selectionBox.Alpha = SelectedRoom.Value == Room ? 1 : 0; - SelectedRoom.BindValueChanged(updateSelectedRoom, true); + SelectedRoom.BindValueChanged(updateSelectedRoom); } private void updateSelectedRoom(ValueChangedEvent selected) From 7ed995fbc504f67bc009d0a79ede13e985e0589d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 19:38:47 +0900 Subject: [PATCH 077/207] Add test with many rooms displayed --- .../Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index dafa8300f6..5c248163d7 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -32,6 +32,12 @@ namespace osu.Game.Tests.Visual.Playlists private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType().First(); + [Test] + public void TestManyRooms() + { + AddStep("add rooms", () => RoomManager.AddRooms(500)); + } + [Test] public void TestScrollByDraggingRooms() { From 7941240a007696d64f77c62afd561e2119c7808e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 20:51:05 +0900 Subject: [PATCH 078/207] Revert "Load `DrawableLoungRoom`s asynchronously" This reverts commit 0b55bb6913fbf05ebb8ecadfe711084d6797efe0. --- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 8e3c74aba9..76cb02199b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -64,9 +64,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override void LoadComplete() { + rooms.CollectionChanged += roomsChanged; roomManager.RoomsUpdated += updateSorting; - rooms.CollectionChanged += roomsChanged; rooms.BindTo(roomManager.Rooms); Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true); @@ -108,14 +108,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void addRooms(IEnumerable rooms) { - LoadComponentsAsync(rooms.Select(room => - new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } }), rooms => - { - // check against rooms collection to ensure the room wasn't removed since this async load started. - roomFlow.AddRange(rooms.Where(r => this.rooms.Contains(r.Room))); + foreach (var room in rooms) + roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } }); - applyFilterCriteria(Filter?.Value); - }); + applyFilterCriteria(Filter?.Value); } private void removeRooms(IEnumerable rooms) From 136573982c114ec5b626e92f76510bd0fdf253b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 20:51:16 +0900 Subject: [PATCH 079/207] Add fade in and fix incorrect wrapper bounds --- .../Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 964a9eddfb..3a3c07fb19 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -227,6 +227,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components }, }, }, 0) + { + RelativeSizeAxes = Axes.Both, + } }; } @@ -236,6 +239,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.DelayedLoadComplete += _ => { + wrapper.FadeInFromZero(200); + roomCategory.BindTo(Room.Category); roomCategory.BindValueChanged(c => { From a622a0b6609bd676cf23a6d536cbc24d998aadf4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 23:07:53 +0900 Subject: [PATCH 080/207] Use a more traditional method of caching --- .../Configuration/SettingSourceAttribute.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 4fff81b587..4346a583ec 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -167,7 +168,30 @@ namespace osu.Game.Configuration } } - public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this T obj) => SettingSourceCache.SettingSourceProperties; + private static readonly ConcurrentDictionary> property_info_cache = new ConcurrentDictionary>(); + + public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) + { + var type = obj.GetType(); + + if (!property_info_cache.TryGetValue(type, out var properties)) + property_info_cache[type] = properties = getSettingsSourceProperties(type); + + return properties; + } + + private static IEnumerable<(SettingSourceAttribute, PropertyInfo)> getSettingsSourceProperties(Type type) + { + foreach (var property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) + { + var attr = property.GetCustomAttribute(true); + + if (attr == null) + continue; + + yield return (attr, property); + } + } public static ICollection<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj) => obj.GetSettingsSourceProperties() @@ -185,14 +209,5 @@ namespace osu.Game.Configuration protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 100); } } - - private static class SettingSourceCache - { - public static (SettingSourceAttribute, PropertyInfo)[] SettingSourceProperties { get; } = - typeof(T).GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance) - .Select(property => (property.GetCustomAttribute(), property)) - .Where(pair => pair.Item1 != null) - .ToArray(); - } } } From 6fc46792d3fecfdd78158f5047ca62d4f9bc65a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 00:39:54 +0900 Subject: [PATCH 081/207] Fix test regressions --- .../Visual/Multiplayer/TestSceneDrawableRoom.cs | 2 ++ .../OnlinePlay/Lounge/Components/DrawableRoom.cs | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 3973dc57b2..b1f5781f6f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -130,6 +130,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Type = { Value = MatchType.HeadToHead }, })); + AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType().Any()); + AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha)); AddStep("set password", () => room.Password.Value = "password"); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 3a3c07fb19..80070aa6ba 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -65,6 +65,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { + ButtonsContainer = new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + }; + InternalChildren = new[] { // This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites. @@ -208,13 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components }, Children = new Drawable[] { - ButtonsContainer = new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X - }, + ButtonsContainer, recentParticipantsList = new RecentParticipantsList { Anchor = Anchor.CentreRight, From ba99a808af624d4a2ae9c4a77dc0f3be8a25f535 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 01:21:19 +0900 Subject: [PATCH 082/207] Use a decoupled clock for triangles intro to avoid startup freezes on broken audio device --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- osu.Game/Screens/Menu/IntroTriangles.cs | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 07a94fb97e..ac4a53f4a9 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); - protected void StartTrack() + protected virtual void StartTrack() { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. if (UsingThemedIntro) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 0ea83fe5e7..69333d4a12 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Menu private Sample welcome; + private DecoupleableInterpolatingFramedClock decoupledClock; + [BackgroundDependencyLoader] private void load() { @@ -56,10 +58,18 @@ namespace osu.Game.Screens.Menu { PrepareMenuLoad(); + decoupledClock = new DecoupleableInterpolatingFramedClock + { + IsCoupled = false + }; + + if (UsingThemedIntro) + decoupledClock.ChangeSource(Track); + LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(UsingThemedIntro ? Track : null), + Clock = decoupledClock, LoadMenu = LoadMenu }, t => { @@ -78,6 +88,12 @@ namespace osu.Game.Screens.Menu background.FadeOut(100); } + protected override void StartTrack() + { + if (UsingThemedIntro) + decoupledClock.Start(); + } + private class TrianglesIntroSequence : CompositeDrawable { private readonly OsuLogo logo; From 9b05bf3a2cf8db1fc9c18e06cbe9f7952110bef0 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 8 Sep 2021 11:43:59 -0700 Subject: [PATCH 083/207] Fix volume meter not being highlighted when hovering before show --- osu.Game/Overlays/Volume/VolumeMeter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index f4cbbf5a00..7249dd77e5 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -355,6 +355,12 @@ namespace osu.Game.Overlays.Volume return base.OnMouseMove(e); } + protected override bool OnHover(HoverEvent e) + { + State = SelectionState.Selected; + return false; + } + protected override void OnHoverLost(HoverLostEvent e) { } From 2888623bdb417a707ddb76939d27f6b7d65bf985 Mon Sep 17 00:00:00 2001 From: Emil Olesen Date: Wed, 8 Sep 2021 22:02:24 +0200 Subject: [PATCH 084/207] Extended the width of the ResetSectionButton to be equal to Content.Width. Fixes #14685 --- .../Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index ef5ccae1a0..2e49671669 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Text = InputSettingsStrings.ResetSectionButton; RelativeSizeAxes = Axes.X; - Width = 0.5f; + Width = Content.Width; Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; Margin = new MarginPadding { Top = 15 }; From 46a2e6ce424b03a6a411ef485ee26352c5d80e65 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 8 Sep 2021 14:57:38 -0700 Subject: [PATCH 085/207] Specify minimum windows version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f922f74a7..d1ad63663f 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. -- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs. +- When running on Windows 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. ## Developing a custom ruleset From ff4d890a441e198e77cd508a36a218ea78760640 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 8 Sep 2021 15:01:53 -0700 Subject: [PATCH 086/207] Normalise format of listed OSes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1ad63663f..f4313eea8c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. From 31bb2055f9b825b7afc1a0f4e660d4451d66fad3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 8 Sep 2021 15:06:39 -0700 Subject: [PATCH 087/207] Remove stray space on table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f4313eea8c..9dd5280d13 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. From 34a8607b20b6e95eeaaa7630d7d17a38f6ca5cae Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 8 Sep 2021 20:08:25 -0700 Subject: [PATCH 088/207] Remove unnecessary prerequisites for windows 8.1 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9dd5280d13..786ce2589d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ If you are looking to install or test osu! without setting up a development envi - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. -- When running on Windows 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. ## Developing a custom ruleset From 9a0dbaa8e34152fefd7a18fe33e111cb8539b26a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 12:39:28 +0900 Subject: [PATCH 089/207] Remove default parameter from build benchmark project configuration --- .../.idea/runConfigurations/Benchmarks.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml index 8fa7608b8e..498a710df9 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml @@ -1,8 +1,8 @@ - \ No newline at end of file From 210640af0924f5e77ccad3633d06f7b898cb86f7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 9 Sep 2021 12:39:40 +0900 Subject: [PATCH 090/207] Fix overlay not refreshed in `TestSceneBeatmapSetOverlay` --- .../Visual/Online/TestSceneBeatmapSetOverlay.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index de7e4075f3..40c8fae50a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -11,6 +11,7 @@ using osu.Game.Users; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Utils; namespace osu.Game.Tests.Visual.Online { @@ -240,7 +241,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("show explicit map", () => { - var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var beatmapSet = getBeatmapSet(); beatmapSet.OnlineInfo.HasExplicitContent = true; overlay.ShowBeatmapSet(beatmapSet); }); @@ -251,7 +252,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("show featured map", () => { - var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var beatmapSet = getBeatmapSet(); beatmapSet.OnlineInfo.TrackId = 1; overlay.ShowBeatmapSet(beatmapSet); }); @@ -319,6 +320,14 @@ namespace osu.Game.Tests.Visual.Online }; } + private BeatmapSetInfo getBeatmapSet() + { + var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + // Overlay doesn't reload if the same beatmap set is set. + beatmapSet.OnlineBeatmapSetID = RNG.Next(); + return beatmapSet; + } + private void downloadAssert(bool shown) { AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown); From 45a534a1ba0f1c836215c5a4b1578da733c50fdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 12:39:45 +0900 Subject: [PATCH 091/207] Fix duplicated `GlobalSetup` attribute on `BenchmarkMod` --- osu.Game.Benchmarks/BenchmarkMod.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs index 050ddf36d5..c5375e9f09 100644 --- a/osu.Game.Benchmarks/BenchmarkMod.cs +++ b/osu.Game.Benchmarks/BenchmarkMod.cs @@ -14,9 +14,9 @@ namespace osu.Game.Benchmarks [Params(1, 10, 100)] public int Times { get; set; } - [GlobalSetup] - public void GlobalSetup() + public override void SetUp() { + base.SetUp(); mod = new OsuModDoubleTime(); } From 278584de99e416aed21ef3034a88d072d61de2c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 12:40:05 +0900 Subject: [PATCH 092/207] Disable optimisations validator (in line with framework project) --- osu.Game.Benchmarks/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Benchmarks/Program.cs b/osu.Game.Benchmarks/Program.cs index c55075fea6..439ced53ab 100644 --- a/osu.Game.Benchmarks/Program.cs +++ b/osu.Game.Benchmarks/Program.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 BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; namespace osu.Game.Benchmarks @@ -11,7 +12,7 @@ namespace osu.Game.Benchmarks { BenchmarkSwitcher .FromAssembly(typeof(Program).Assembly) - .Run(args); + .Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true)); } } } From 52bb02baed7538157654dd7e9b470842862c6e70 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 9 Sep 2021 12:51:21 +0900 Subject: [PATCH 093/207] Prefer composite over inheritance for drawable parts --- .../BeatmapSet/BeatmapSetBadgePill.cs | 32 ------------- .../BeatmapSetBadgePillContainer.cs | 45 +++++++++++++++++++ .../BeatmapSet/ExplicitContentBeatmapPill.cs | 22 ++++----- .../BeatmapSet/FeaturedArtistBeatmapPill.cs | 22 ++++----- 4 files changed, 69 insertions(+), 52 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePill.cs create mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePillContainer.cs diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePill.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePill.cs deleted file mode 100644 index 15c691103a..0000000000 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePill.cs +++ /dev/null @@ -1,32 +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 JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class BeatmapSetBadgePill : CircularContainer - { - public BeatmapSetBadgePill() - { - AutoSizeAxes = Axes.Both; - Masking = true; - } - - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OsuColour colours, [CanBeNull] OverlayColourProvider colourProvider) - { - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider?.Background5 ?? colours?.Gray2 ?? Color4.DarkGray, - }); - } - } -} diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePillContainer.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePillContainer.cs new file mode 100644 index 0000000000..62bbc35a2d --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePillContainer.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapSetBadgePillContainer : CircularContainer + { + protected override Container Content => contentContainer; + + private readonly Box background; + private readonly Container contentContainer; + + public BeatmapSetBadgePillContainer() + { + Masking = true; + AutoSizeAxes = Axes.Both; + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + contentContainer = new Container + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 10f, Vertical = 2f }, + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OsuColour colours, [CanBeNull] OverlayColourProvider colourProvider) + { + background.Colour = colourProvider?.Background5 ?? colours?.Gray2 ?? Color4.DarkGray; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs index 60fa3ea900..4bd3f132f9 100644 --- a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs @@ -1,27 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet { - public class ExplicitContentBeatmapPill : BeatmapSetBadgePill + public class ExplicitContentBeatmapPill : CompositeDrawable { - [BackgroundDependencyLoader] - private void load() + public ExplicitContentBeatmapPill() { - Add(new OsuSpriteText + AutoSizeAxes = Axes.Both; + InternalChild = new BeatmapSetBadgePillContainer { - Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, - Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), - Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Orange.Colour2, - }); + Child = new OsuSpriteText + { + Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Colour = OverlayColourProvider.Orange.Colour2, + } + }; } } } diff --git a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs index 7facc2953a..a9950b1b60 100644 --- a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs @@ -1,27 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet { - public class FeaturedArtistBeatmapPill : BeatmapSetBadgePill + public class FeaturedArtistBeatmapPill : CompositeDrawable { - [BackgroundDependencyLoader] - private void load() + public FeaturedArtistBeatmapPill() { - Add(new OsuSpriteText + AutoSizeAxes = Axes.Both; + InternalChild = new BeatmapSetBadgePillContainer { - Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, - Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), - Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Blue.Colour1 - }); + Child = new OsuSpriteText + { + Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Colour = OverlayColourProvider.Blue.Colour1 + } + }; } } } From 41a2e6eeeba92bfd9e0b1d8e896f6c283d3d6e1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 14:56:39 +0900 Subject: [PATCH 094/207] Fix cache not actually caching anything --- osu.Game/Configuration/SettingSourceAttribute.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 4346a583ec..5db502804d 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -168,14 +168,14 @@ namespace osu.Game.Configuration } } - private static readonly ConcurrentDictionary> property_info_cache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary property_info_cache = new ConcurrentDictionary(); public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) { var type = obj.GetType(); if (!property_info_cache.TryGetValue(type, out var properties)) - property_info_cache[type] = properties = getSettingsSourceProperties(type); + property_info_cache[type] = properties = getSettingsSourceProperties(type).ToArray(); return properties; } From efaf07dbc8db673737ab14d80f37f8e0fcf18f82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 14:47:19 +0900 Subject: [PATCH 095/207] Add benchmark coverage of mod retrieval --- osu.Game.Benchmarks/BenchmarkRuleset.cs | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkRuleset.cs diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs new file mode 100644 index 0000000000..1b315b5001 --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using BenchmarkDotNet.Attributes; +using osu.Game.Online.API; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkRuleset : BenchmarkTest + { + private OsuRuleset ruleset; + private APIMod apiModDoubleTime; + private APIMod apiModDifficultyAdjust; + + public override void SetUp() + { + base.SetUp(); + ruleset = new OsuRuleset(); + apiModDoubleTime = new APIMod { Acronym = "DT" }; + apiModDifficultyAdjust = new APIMod { Acronym = "DA" }; + } + + [Benchmark] + public void BenchmarkToModDoubleTime() + { + apiModDoubleTime.ToMod(ruleset); + } + + [Benchmark] + public void BenchmarkToModDifficultyAdjust() + { + apiModDifficultyAdjust.ToMod(ruleset); + } + + [Benchmark] + public void BenchmarkGetAllMods() + { + ruleset.GetAllMods(); + } + } +} From 4708cb7317f21287ab7448d9209d6c7eedf4681a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 15:15:18 +0900 Subject: [PATCH 096/207] Fix enumerable not being consumed --- osu.Game.Benchmarks/BenchmarkRuleset.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index 1b315b5001..63c99dcb2b 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; @@ -36,7 +37,7 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkGetAllMods() { - ruleset.GetAllMods(); + ruleset.GetAllMods().Consume(new Consumer()); } } } From b01cf5c937bad88ffea9ad1df75bf9f0dfb813dd Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 9 Sep 2021 15:33:47 +0900 Subject: [PATCH 097/207] Don't play hover/select sounds for UpdatableAvatar unless it's interactable --- osu.Game/Users/Drawables/UpdateableAvatar.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index df724404e9..a8726d0cab 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -69,14 +69,20 @@ namespace osu.Game.Users.Drawables if (user == null && !showGuestOnNull) return null; - var avatar = new ClickableAvatar(user) + if (!openOnClick) + { + return new DrawableAvatar(user) + { + RelativeSizeAxes = Axes.Both, + }; + } + + return new ClickableAvatar(user) { OpenOnClick = openOnClick, ShowUsernameTooltip = showUsernameTooltip, RelativeSizeAxes = Axes.Both, }; - - return avatar; } } } From e66d76d26e2979cd5a858aa2aa2b0061c36856ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 15:43:55 +0900 Subject: [PATCH 098/207] Avoid settings copy if there are no settings --- osu.Game/Online/API/APIMod.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 4427c82a8b..596d567480 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -53,12 +53,15 @@ namespace osu.Game.Online.API if (resultMod == null) throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); - foreach (var (_, property) in resultMod.GetSettingsSourceProperties()) + if (Settings.Count > 0) { - if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) - continue; + foreach (var (_, property) in resultMod.GetSettingsSourceProperties()) + { + if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) + continue; - resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue); + resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue); + } } return resultMod; From 4d0530ca9d1b2d110fc407bac3cc112161367bfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 16:34:49 +0900 Subject: [PATCH 099/207] Add new methods to ruleset for quicker mod lookups --- osu.Game.Benchmarks/BenchmarkRuleset.cs | 6 +++ .../Components/TournamentModIcon.cs | 2 +- osu.Game/Online/API/APIMod.cs | 2 +- .../Requests/Responses/APILegacyScoreInfo.cs | 2 +- .../BeatmapSet/LeaderboardModSelector.cs | 2 +- .../IncompatibilityDisplayingModButton.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 40 +++++++++++++++++++ 7 files changed, 51 insertions(+), 5 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index 63c99dcb2b..f3d678a2b0 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -39,5 +39,11 @@ namespace osu.Game.Benchmarks { ruleset.GetAllMods().Consume(new Consumer()); } + + [Benchmark] + public void BenchmarkGetAllModsForReference() + { + ruleset.GetAllModsForReference().Consume(new Consumer()); + } } } diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index 43ac92d285..709e99b165 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tournament.Components } var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); - var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym); + var modIcon = ruleset?.CreateInstance().GetModForAcronym(modAcronym); if (modIcon == null) return; diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 596d567480..f31f8bc92a 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online.API public Mod ToMod(Ruleset ruleset) { - Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == Acronym); + Mod resultMod = ruleset.GetModForAcronym(Acronym); if (resultMod == null) throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 1b394185fd..e74db7de6e 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -23,7 +23,7 @@ namespace osu.Game.Online.API.Requests.Responses var rulesetInstance = ruleset.CreateInstance(); - var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty(); + var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.GetModForAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); // all API scores provided by this class are considered to be legacy. mods = mods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray(); diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 5b903372fd..517da11c8c 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.UserPlayable).Select(m => new ModButton(m))); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllModsForReference().Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.ForEach(button => { diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs index c8e44ee159..8d0746f24d 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs @@ -107,7 +107,7 @@ namespace osu.Game.Overlays.Mods var incompatibleTypes = mod.IncompatibleMods; - var allMods = ruleset.Value.CreateInstance().GetAllMods(); + var allMods = ruleset.Value.CreateInstance().GetAllModsForReference(); incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index de62cf8d33..2f65bd76a4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; @@ -23,6 +24,7 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; @@ -38,6 +40,13 @@ namespace osu.Game.Rulesets { public RulesetInfo RulesetInfo { get; internal set; } + /// + /// Returns fresh instances of all mods. + /// + /// + /// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings) + /// use instead. + /// public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast() // Confine all mods of each mod type into a single IEnumerable .SelectMany(GetModsFor) @@ -46,6 +55,37 @@ namespace osu.Game.Rulesets // Resolve MultiMods as their .Mods property .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); + private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); + + /// + /// Returns all mods for a query-only purpose. + /// Bindables should not be considered usable when retrieving via this method (use instead). + /// + public IEnumerable GetAllModsForReference() + { + if (!(RulesetInfo.ID is int id)) + return GetAllMods(); + + if (!mod_reference_cache.TryGetValue(id, out var mods)) + mod_reference_cache[id] = mods = GetAllMods().ToArray(); + + return mods; + } + + /// + /// Returns a fresh instance of the mod matching the specified acronym. + /// + /// The acronym to query for . + public Mod GetModForAcronym(string acronym) + { + var type = GetAllModsForReference().FirstOrDefault(m => m.Acronym == acronym)?.GetType(); + + if (type != null) + return (Mod)Activator.CreateInstance(type); + + return null; + } + public abstract IEnumerable GetModsFor(ModType type); /// From 2edb8510081ee30e14f3c9feec7451319acd9e0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 16:46:24 +0900 Subject: [PATCH 100/207] Add ability to lookup mod from a type specification --- osu.Game.Benchmarks/BenchmarkRuleset.cs | 13 +++++++++++++ .../Components/TournamentModIcon.cs | 1 - .../Requests/Responses/APILegacyScoreInfo.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 17 +++++++++++++++-- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 +- 7 files changed, 32 insertions(+), 7 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index f3d678a2b0..aa59cb7fa3 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -4,6 +4,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using osu.Game.Online.API; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; namespace osu.Game.Benchmarks @@ -45,5 +46,17 @@ namespace osu.Game.Benchmarks { ruleset.GetAllModsForReference().Consume(new Consumer()); } + + [Benchmark] + public void BenchmarkGetForAcronym() + { + ruleset.GetModForAcronym("DT"); + } + + [Benchmark] + public void BenchmarkGetForType() + { + ruleset.GetMod(); + } } } diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index 709e99b165..b8486b5b2d 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index e74db7de6e..d1c6f0a55a 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests.Responses var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.GetModForAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); // all API scores provided by this class are considered to be legacy. - mods = mods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray(); + mods = mods.Append(rulesetInstance.GetMod()).ToArray(); var scoreInfo = new ScoreInfo { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f4db0f2603..d52a07266c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -166,7 +166,7 @@ namespace osu.Game public OsuGameBase() { - UseDevelopmentServer = DebugUtils.IsDebugBuild; + UseDevelopmentServer = false; Name = @"osu!"; } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 2f65bd76a4..34e4606133 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -24,7 +24,6 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; using JetBrains.Annotations; -using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; @@ -86,6 +85,20 @@ namespace osu.Game.Rulesets return null; } + /// + /// Returns a fresh instance of the mod matching the specified type. + /// + public T GetMod() + where T : Mod + { + var type = GetAllModsForReference().FirstOrDefault(m => m is T)?.GetType(); + + if (type != null) + return (T)Activator.CreateInstance(type); + + return null; + } + public abstract IEnumerable GetModsFor(ModType type); /// @@ -166,7 +179,7 @@ namespace osu.Game.Rulesets } [CanBeNull] - public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); + public ModAutoplay GetAutoplayMod() => GetMod(); public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 2f17167297..010f33e3ed 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -67,7 +67,7 @@ namespace osu.Game.Scoring.Legacy // lazer replays get a really high version number. if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) - scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetAllMods().OfType().Single()).ToArray(); + scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetMod()).ToArray(); currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 93491c800f..a31a6433ea 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual if (!AllowFail) { - var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); + var noFailMod = ruleset.GetMod(); if (noFailMod != null) SelectedMods.Value = new[] { noFailMod }; } From 9b34ffc3029e20027f32ac866587853d60d76eeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 17:12:38 +0900 Subject: [PATCH 101/207] Undo big oopsie --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d52a07266c..f4db0f2603 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -166,7 +166,7 @@ namespace osu.Game public OsuGameBase() { - UseDevelopmentServer = false; + UseDevelopmentServer = DebugUtils.IsDebugBuild; Name = @"osu!"; } From 29f947fa07ac5a550f8470a0b178117021144d9a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 9 Sep 2021 19:25:30 +0900 Subject: [PATCH 102/207] Revert `ExplicitContentBeatmapPill`, don't try to reuse common code --- .../BeatmapSetBadgePillContainer.cs | 45 ------------------- .../BeatmapSet/ExplicitContentBeatmapPill.cs | 28 +++++++++--- .../BeatmapSet/FeaturedArtistBeatmapPill.cs | 28 +++++++++--- 3 files changed, 46 insertions(+), 55 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePillContainer.cs diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePillContainer.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePillContainer.cs deleted file mode 100644 index 62bbc35a2d..0000000000 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetBadgePillContainer.cs +++ /dev/null @@ -1,45 +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 JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class BeatmapSetBadgePillContainer : CircularContainer - { - protected override Container Content => contentContainer; - - private readonly Box background; - private readonly Container contentContainer; - - public BeatmapSetBadgePillContainer() - { - Masking = true; - AutoSizeAxes = Axes.Both; - InternalChildren = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - contentContainer = new Container - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 10f, Vertical = 2f }, - } - }; - } - - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OsuColour colours, [CanBeNull] OverlayColourProvider colourProvider) - { - background.Colour = colourProvider?.Background5 ?? colours?.Gray2 ?? Color4.DarkGray; - } - } -} diff --git a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs index 4bd3f132f9..ba78592ed2 100644 --- a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; @@ -15,13 +17,29 @@ namespace osu.Game.Overlays.BeatmapSet public ExplicitContentBeatmapPill() { AutoSizeAxes = Axes.Both; - InternalChild = new BeatmapSetBadgePillContainer + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider colourProvider) + { + InternalChild = new CircularContainer { - Child = new OsuSpriteText + Masking = true, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), - Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Orange.Colour2, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider?.Background5 ?? colours.Gray2, + }, + new OsuSpriteText + { + Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, + Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Colour = OverlayColourProvider.Orange.Colour2, + } } }; } diff --git a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs index a9950b1b60..fdee0799ff 100644 --- a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; @@ -15,13 +17,29 @@ namespace osu.Game.Overlays.BeatmapSet public FeaturedArtistBeatmapPill() { AutoSizeAxes = Axes.Both; - InternalChild = new BeatmapSetBadgePillContainer + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider colourProvider) + { + InternalChild = new CircularContainer { - Child = new OsuSpriteText + Masking = true, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), - Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Blue.Colour1 + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider?.Background5 ?? colours.Gray2, + }, + new OsuSpriteText + { + Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, + Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Colour = OverlayColourProvider.Blue.Colour1, + } } }; } From a2c2646230bd6d92e057238d9c7298fa2f291220 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 9 Sep 2021 19:36:47 +0900 Subject: [PATCH 103/207] Use a counter instead of RNG --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 40c8fae50a..f420ad976b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -11,7 +11,6 @@ using osu.Game.Users; using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Utils; namespace osu.Game.Tests.Visual.Online { @@ -22,6 +21,8 @@ namespace osu.Game.Tests.Visual.Online protected override bool UseOnlineAPI => true; + private int nextBeatmapSetId = 1; + public TestSceneBeatmapSetOverlay() { Add(overlay = new TestBeatmapSetOverlay()); @@ -323,8 +324,8 @@ namespace osu.Game.Tests.Visual.Online private BeatmapSetInfo getBeatmapSet() { var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; - // Overlay doesn't reload if the same beatmap set is set. - beatmapSet.OnlineBeatmapSetID = RNG.Next(); + // Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`). + beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++; return beatmapSet; } From 99b6f0352cf05f733e374a1889a3c416a1b42332 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 22:04:00 +0900 Subject: [PATCH 104/207] Always start decoupled clock regardless of track source --- osu.Game/Screens/Menu/IntroTriangles.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 69333d4a12..36296487a8 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -90,8 +90,7 @@ namespace osu.Game.Screens.Menu protected override void StartTrack() { - if (UsingThemedIntro) - decoupledClock.Start(); + decoupledClock.Start(); } private class TrianglesIntroSequence : CompositeDrawable From 6c18df24ec432d848ba8c6b3b6e8d44691b48aad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Sep 2021 22:04:16 +0900 Subject: [PATCH 105/207] Change how `UsingThemedIntro` is set to improve clarity --- osu.Game/Screens/Menu/IntroScreen.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index ac4a53f4a9..cfe14eab92 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -115,7 +115,9 @@ namespace osu.Game.Screens.Menu if (setInfo == null) return false; - return (initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0])) != null; + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + + return UsingThemedIntro = initialBeatmap != null; } } @@ -184,7 +186,6 @@ namespace osu.Game.Screens.Menu { beatmap.Value = initialBeatmap; Track = initialBeatmap.Track; - UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice; // ensure the track starts at maximum volume musicController.CurrentTrack.FinishTransforms(); From 2e00c7184206c190fedce24245dc0b852ecfb52e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 01:57:33 +0900 Subject: [PATCH 106/207] Add failing test coverage --- .../Online/TestSceneBeatmapListingOverlay.cs | 90 +++++++++++++++---- 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 5bfb676f81..abadfac76a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -17,10 +17,11 @@ using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Users; +using osuTK.Input; namespace osu.Game.Tests.Visual.Online { - public class TestSceneBeatmapListingOverlay : OsuTestScene + public class TestSceneBeatmapListingOverlay : OsuManualInputManagerTestScene { private readonly List setsForResponse = new List(); @@ -28,27 +29,33 @@ namespace osu.Game.Tests.Visual.Online private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single(); - [BackgroundDependencyLoader] - private void load() + [SetUpSteps] + public void SetUpSteps() { - Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } }; - - ((DummyAPIAccess)API).HandleRequest = req => + AddStep("setup overlay", () => { - if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false; - - searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse - { - BeatmapSets = setsForResponse, - }); - - return true; - }; + Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } }; + setsForResponse.Clear(); + }); AddStep("initialize dummy", () => { + var api = (DummyAPIAccess)API; + + api.HandleRequest = req => + { + if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false; + + searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse + { + BeatmapSets = setsForResponse, + }); + + return true; + }; + // non-supporter user - ((DummyAPIAccess)API).LocalUser.Value = new User + api.LocalUser.Value = new User { Username = "TestBot", Id = API.LocalUser.Value.Id + 1, @@ -56,6 +63,51 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestHideViaBack() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + AddStep("hide", () => InputManager.Key(Key.Escape)); + AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); + } + + [Test] + public void TestHideViaBackWithSearch() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("search something", () => overlay.ChildrenOfType().First().Text = "search"); + + AddStep("kill search", () => InputManager.Key(Key.Escape)); + + AddAssert("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType().First().Text)); + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("hide", () => InputManager.Key(Key.Escape)); + AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); + } + + [Test] + public void TestHideViaBackWithScrolledSearch() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, 100).ToArray())); + + AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("scroll to bottom", () => overlay.ChildrenOfType().First().ScrollToEnd()); + + AddStep("kill search", () => InputManager.Key(Key.Escape)); + + AddUntilStep("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType().First().Text)); + AddUntilStep("is scrolled to top", () => overlay.ChildrenOfType().First().Current == 0); + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("hide", () => InputManager.Key(Key.Escape)); + AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); + } + [Test] public void TestNoBeatmapsPlaceholder() { @@ -193,13 +245,15 @@ namespace osu.Game.Tests.Visual.Online noPlaceholderShown(); } + private static int searchCount; + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); // trigger arbitrary change for fetching. - searchControl.Query.TriggerChange(); + searchControl.Query.Value = $"search {searchCount++}"; } private void setRankAchievedFilter(ScoreRank[] ranks) From c101d1f20570829380b9952d0dd99999118eb3bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 01:57:55 +0900 Subject: [PATCH 107/207] Fix beatmap listing overlay not hiding via keyboard control when scrolled Closes https://github.com/ppy/osu/issues/14684. --- .../Graphics/UserInterface/FocusedTextBox.cs | 2 +- .../BeatmapListingSearchControl.cs | 24 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index ed9f0710b0..6c8238a1b8 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface return base.OnKeyDown(e); } - public bool OnPressed(GlobalAction action) + public virtual bool OnPressed(GlobalAction action) { if (!HasFocus) return false; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index d1e2ac38df..07a0cfb57f 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -3,21 +3,22 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osuTK; -using osu.Framework.Bindables; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Resources.Localisation.Web; -using osuTK.Graphics; using osu.Game.Rulesets; using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapListing { @@ -117,7 +118,7 @@ namespace osu.Game.Overlays.BeatmapListing textBox = new BeatmapSearchTextBox { RelativeSizeAxes = Axes.X, - TypingStarted = () => TypingStarted?.Invoke(), + TextChanged = () => TypingStarted?.Invoke(), }, new ReverseChildIDFillFlowContainer { @@ -167,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapListing /// /// Any time the text box receives key events (even while masked). /// - public Action TypingStarted; + public Action TextChanged; protected override Color4 SelectionColour => Color4.Gray; @@ -181,7 +182,16 @@ namespace osu.Game.Overlays.BeatmapListing if (!base.OnKeyDown(e)) return false; - TypingStarted?.Invoke(); + TextChanged?.Invoke(); + return true; + } + + public override bool OnPressed(GlobalAction action) + { + if (!base.OnPressed(action)) + return false; + + TextChanged?.Invoke(); return true; } } From 3865988e48d1ef98ddcaceb947518af7eaa8f0b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 02:15:13 +0900 Subject: [PATCH 108/207] Add test coverage for back button support in password popover --- .../TestSceneMultiplayerLoungeSubScreen.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 99f6ab1ae1..61565c88f4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -51,6 +51,24 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room join password correct", () => lastJoinedPassword == null); } + [Test] + public void TestPopoverHidesOnBackButton() + { + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("attempt join room", () => InputManager.Key(Key.Enter)); + + AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType().Any()); + + AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox); + + AddStep("hit escape", () => InputManager.Key(Key.Escape)); + AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox); + + AddStep("hit escape", () => InputManager.Key(Key.Escape)); + AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any()); + } + [Test] public void TestPopoverHidesOnLeavingScreen() { From 344bf2ab7c1038d4b8e64e7645f03bc93085713a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 02:15:30 +0900 Subject: [PATCH 109/207] Allow popovers to be closed via back button press Closes https://github.com/ppy/osu/issues/14669. --- .../Graphics/UserInterfaceV2/OsuPopover.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index c07a5de1e4..fac0661a15 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -4,14 +4,17 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osuTK; namespace osu.Game.Graphics.UserInterfaceV2 { - public class OsuPopover : Popover + public class OsuPopover : Popover, IKeyBindingHandler { private const float fade_duration = 250; private const double scale_duration = 500; @@ -51,5 +54,24 @@ namespace osu.Game.Graphics.UserInterfaceV2 this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); this.FadeOut(fade_duration, Easing.OutQuint); } + + public bool OnPressed(GlobalAction action) + { + if (State.Value == Visibility.Hidden) + return false; + + if (action == GlobalAction.Back) + { + Hide(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + throw new System.NotImplementedException(); + } } } From 32de13cb96f4dd48d8f66980e97af28fdd9eac86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 9 Sep 2021 21:33:02 +0200 Subject: [PATCH 110/207] Use consistent assertions for checking placeholder presence --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index abadfac76a..963809ebe1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, 100).ToArray())); - AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent)); AddStep("scroll to bottom", () => overlay.ChildrenOfType().First().ScrollToEnd()); @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); - AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent)); AddStep("fetch for 0 beatmaps", () => fetchFor()); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); @@ -283,8 +283,8 @@ namespace osu.Game.Tests.Visual.Online private void noPlaceholderShown() { AddUntilStep("no placeholder shown", () => - !overlay.ChildrenOfType().Any() - && !overlay.ChildrenOfType().Any()); + !overlay.ChildrenOfType().Any(d => d.IsPresent) + && !overlay.ChildrenOfType().Any(d => d.IsPresent)); } private class TestAPIBeatmapSet : APIBeatmapSet From ce6b022a9042d56f59c1c7fc8eae19377598bc87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 10:59:30 +0900 Subject: [PATCH 111/207] Remove unused `IMod` specification from `APIMod` --- osu.Game/Online/API/APIMod.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index f31f8bc92a..5514ce5e40 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -16,7 +16,7 @@ using osu.Game.Utils; namespace osu.Game.Online.API { [MessagePackObject] - public class APIMod : IMod, IEquatable + public class APIMod { [JsonProperty("acronym")] [Key(0)] @@ -67,8 +67,6 @@ namespace osu.Game.Online.API return resultMod; } - public bool Equals(IMod other) => other is APIMod them && Equals(them); - public bool Equals(APIMod other) { if (ReferenceEquals(null, other)) return false; From cf633973a91dc977c0dec6d8cf2f020b1dbc3807 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 11:09:13 +0900 Subject: [PATCH 112/207] Refactor exposed mod retrieval methods for better safety --- osu.Game.Benchmarks/BenchmarkRuleset.cs | 8 +-- .../Gameplay/TestSceneAllRulesetPlayers.cs | 2 +- .../SongSelect/TestSceneAdvancedStats.cs | 8 +-- .../TestSceneBeatmapMetadataDisplay.cs | 2 +- .../UserInterface/TestSceneModFlowDisplay.cs | 2 +- .../TestSceneModSelectOverlay.cs | 4 +- .../TestSceneTournamentModDisplay.cs | 2 +- .../Components/TournamentModIcon.cs | 2 +- osu.Game/Online/API/APIMod.cs | 2 +- .../Requests/Responses/APILegacyScoreInfo.cs | 4 +- .../BeatmapSet/LeaderboardModSelector.cs | 2 +- .../IncompatibilityDisplayingModButton.cs | 4 +- osu.Game/Rulesets/Mods/IMod.cs | 19 +++++- osu.Game/Rulesets/Mods/Mod.cs | 7 --- osu.Game/Rulesets/Ruleset.cs | 63 ++++++++++--------- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- .../Tests/Beatmaps/LegacyModConversionTest.cs | 2 +- osu.Game/Tests/TestScoreInfo.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 +- 19 files changed, 75 insertions(+), 64 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index aa59cb7fa3..2835ec9499 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -38,25 +38,25 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkGetAllMods() { - ruleset.GetAllMods().Consume(new Consumer()); + ruleset.CreateAllMods().Consume(new Consumer()); } [Benchmark] public void BenchmarkGetAllModsForReference() { - ruleset.GetAllModsForReference().Consume(new Consumer()); + ruleset.AllMods.Consume(new Consumer()); } [Benchmark] public void BenchmarkGetForAcronym() { - ruleset.GetModForAcronym("DT"); + ruleset.CreateModFromAcronym("DT"); } [Benchmark] public void BenchmarkGetForType() { - ruleset.GetMod(); + ruleset.CreateMod(); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs index b7dcad3825..8487eecdfa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Gameplay var working = CreateWorkingBeatmap(rulesetInfo); Beatmap.Value = working; - SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + SelectedMods.Value = new[] { ruleset.CreateAllMods().First(m => m is ModNoFail) }; Player = CreatePlayer(ruleset); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 40b2f66d74..5e22bde1d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select EZ mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() }; + SelectedMods.Value = new[] { ruleset.CreateAllMods().OfType().Single() }; }); AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select HR mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() }; + SelectedMods.Value = new[] { ruleset.CreateAllMods().OfType().Single() }; }); AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select unchanged Difficulty Adjust mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single(); + var difficultyAdjustMod = ruleset.CreateAllMods().OfType().Single(); difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty); SelectedMods.Value = new[] { difficultyAdjustMod }; }); @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select changed Difficulty Adjust mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single(); + var difficultyAdjustMod = ruleset.CreateAllMods().OfType().Single(); var originalDifficulty = advancedStats.Beatmap.BaseDifficulty; difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 66ac700c51..f91d3f595b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("setup display", () => { - var randomMods = Ruleset.Value.CreateInstance().GetAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList(); + var randomMods = Ruleset.Value.CreateInstance().CreateAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList(); OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs index 8f057c663b..10eab148de 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface Width = 200, Current = { - Value = new OsuRuleset().GetAllMods().ToArray(), + Value = new OsuRuleset().CreateAllMods().ToArray(), } }; }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9e253e089d..28e5644998 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface var mania = new ManiaRuleset(); testModsWithSameBaseType( - mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)), - mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden))); + mania.CreateAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)), + mania.CreateAllMods().Single(m => m.GetType() == typeof(ManiaModHidden))); } [Test] diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index b4d9fa4222..cc1568f429 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components private void success(APIBeatmap apiBeatmap) { beatmap = apiBeatmap.ToBeatmap(rulesets); - var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods(); + var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().CreateAllMods(); foreach (var mod in mods) { diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index b8486b5b2d..7c4e9c69a2 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tournament.Components } var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); - var modIcon = ruleset?.CreateInstance().GetModForAcronym(modAcronym); + var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym); if (modIcon == null) return; diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 5514ce5e40..5e552dac09 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online.API public Mod ToMod(Ruleset ruleset) { - Mod resultMod = ruleset.GetModForAcronym(Acronym); + Mod resultMod = ruleset.CreateModFromAcronym(Acronym); if (resultMod == null) throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index d1c6f0a55a..567df524b1 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -23,10 +23,10 @@ namespace osu.Game.Online.API.Requests.Responses var rulesetInstance = ruleset.CreateInstance(); - var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.GetModForAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); + var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); // all API scores provided by this class are considered to be legacy. - mods = mods.Append(rulesetInstance.GetMod()).ToArray(); + mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); var scoreInfo = new ScoreInfo { diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 517da11c8c..2683d7bc6d 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllModsForReference().Where(m => m.UserPlayable).Select(m => new ModButton(m))); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m.CreateInstance()))); modsContainer.ForEach(button => { diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs index 8d0746f24d..0f51439252 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs @@ -107,9 +107,9 @@ namespace osu.Game.Overlays.Mods var incompatibleTypes = mod.IncompatibleMods; - var allMods = ruleset.Value.CreateInstance().GetAllModsForReference(); + var allMods = ruleset.Value.CreateInstance().AllMods; - incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); + incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList(); incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; } } diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index a5e19f293c..ac17e49e60 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Newtonsoft.Json; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods { @@ -11,7 +11,22 @@ namespace osu.Game.Rulesets.Mods /// /// The shortened name of this mod. /// - [JsonProperty("acronym")] string Acronym { get; } + + /// + /// The icon of this mod. + /// + IconUsage? Icon { get; } + + /// + /// Whether this mod is playable by an end user. + /// Should be false for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example). + /// + bool UserPlayable { get; } + + /// + /// Create a fresh instance based on this mod. + /// + Mod CreateInstance() => ((Mod)this).DeepClone(); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 1199d8a956..fedee857c3 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -33,9 +33,6 @@ namespace osu.Game.Rulesets.Mods /// public abstract string Acronym { get; } - /// - /// The icon of this mod. - /// [JsonIgnore] public virtual IconUsage? Icon => null; @@ -106,10 +103,6 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; - /// - /// Whether this mod is playable by an end user. - /// Should be false for cases where the user is not interacting with the game (so it can be excluded from mutliplayer selection, for example). - /// [JsonIgnore] public virtual bool UserPlayable => true; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 34e4606133..82707ad382 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -39,45 +39,48 @@ namespace osu.Game.Rulesets { public RulesetInfo RulesetInfo { get; internal set; } + private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); + + /// + /// A queryable source containing all available mods. + /// Call for consumption purposes. + /// + public IEnumerable AllMods + { + get + { + if (!(RulesetInfo.ID is int id)) + return CreateAllMods(); + + if (!mod_reference_cache.TryGetValue(id, out var mods)) + mod_reference_cache[id] = mods = CreateAllMods().Cast().ToArray(); + + return mods; + } + } + /// /// Returns fresh instances of all mods. /// /// /// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings) - /// use instead. + /// use instead. /// - public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast() - // Confine all mods of each mod type into a single IEnumerable - .SelectMany(GetModsFor) - // Filter out all null mods - .Where(mod => mod != null) - // Resolve MultiMods as their .Mods property - .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); - - private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); - - /// - /// Returns all mods for a query-only purpose. - /// Bindables should not be considered usable when retrieving via this method (use instead). - /// - public IEnumerable GetAllModsForReference() - { - if (!(RulesetInfo.ID is int id)) - return GetAllMods(); - - if (!mod_reference_cache.TryGetValue(id, out var mods)) - mod_reference_cache[id] = mods = GetAllMods().ToArray(); - - return mods; - } + public IEnumerable CreateAllMods() => Enum.GetValues(typeof(ModType)).Cast() + // Confine all mods of each mod type into a single IEnumerable + .SelectMany(GetModsFor) + // Filter out all null mods + .Where(mod => mod != null) + // Resolve MultiMods as their .Mods property + .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); /// /// Returns a fresh instance of the mod matching the specified acronym. /// /// The acronym to query for . - public Mod GetModForAcronym(string acronym) + public Mod CreateModFromAcronym(string acronym) { - var type = GetAllModsForReference().FirstOrDefault(m => m.Acronym == acronym)?.GetType(); + var type = AllMods.FirstOrDefault(m => m.Acronym == acronym)?.GetType(); if (type != null) return (Mod)Activator.CreateInstance(type); @@ -88,10 +91,10 @@ namespace osu.Game.Rulesets /// /// Returns a fresh instance of the mod matching the specified type. /// - public T GetMod() + public T CreateMod() where T : Mod { - var type = GetAllModsForReference().FirstOrDefault(m => m is T)?.GetType(); + var type = AllMods.FirstOrDefault(m => m is T)?.GetType(); if (type != null) return (T)Activator.CreateInstance(type); @@ -179,7 +182,7 @@ namespace osu.Game.Rulesets } [CanBeNull] - public ModAutoplay GetAutoplayMod() => GetMod(); + public ModAutoplay GetAutoplayMod() => CreateMod(); public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 010f33e3ed..2e1a29372d 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -67,7 +67,7 @@ namespace osu.Game.Scoring.Legacy // lazer replays get a really high version number. if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) - scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetMod()).ToArray(); + scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.CreateMod()).ToArray(); currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs index 54a83f4305..b7803f3420 100644 --- a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps protected void TestToLegacy(LegacyMods expectedLegacyMods, Type[] providedModTypes) { var ruleset = CreateRuleset(); - var modInstances = ruleset.GetAllMods() + var modInstances = ruleset.CreateAllMods() .Where(mod => providedModTypes.Contains(mod.GetType())) .ToArray(); var actualLegacyMods = ruleset.ConvertToLegacyMods(modInstances); diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 8ce71ace69..5ce6aae647 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests RulesetID = ruleset.ID ?? 0; Mods = excessMods - ? ruleset.CreateInstance().GetAllMods().ToArray() + ? ruleset.CreateInstance().CreateAllMods().ToArray() : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; TotalScore = 2845370; diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index a31a6433ea..b34f7e2d5f 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual if (!AllowFail) { - var noFailMod = ruleset.GetMod(); + var noFailMod = ruleset.CreateMod(); if (noFailMod != null) SelectedMods.Value = new[] { noFailMod }; } From 76e877f1607d0aebcbfe3b5f91ded6ef10d941db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 11:22:08 +0900 Subject: [PATCH 113/207] Disable APIMod/Mod cross equality support --- .../Mods/ModSettingsEqualityComparison.cs | 33 ++++++++++++++----- osu.Game/Online/API/APIMod.cs | 5 ++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs index 7a5789f01a..ce6b3a68a5 100644 --- a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs +++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Game.Online.API; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Tests.Mods @@ -11,26 +12,42 @@ namespace osu.Game.Tests.Mods public class ModSettingsEqualityComparison { [Test] - public void Test() + public void TestAPIMod() { + var apiMod1 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.25 } }); + var apiMod2 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }); + var apiMod3 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }); + + Assert.That(apiMod1, Is.Not.EqualTo(apiMod2)); + Assert.That(apiMod2, Is.EqualTo(apiMod2)); + Assert.That(apiMod2, Is.EqualTo(apiMod3)); + Assert.That(apiMod3, Is.EqualTo(apiMod2)); + } + + [Test] + public void TestMod() + { + var ruleset = new OsuRuleset(); + var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } }; var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; - var apiMod1 = new APIMod(mod1); - var apiMod2 = new APIMod(mod2); - var apiMod3 = new APIMod(mod3); + + var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset); + var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset); + var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset); Assert.That(mod1, Is.Not.EqualTo(mod2)); - Assert.That(apiMod1, Is.Not.EqualTo(apiMod2)); + Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2)); Assert.That(mod2, Is.EqualTo(mod2)); - Assert.That(apiMod2, Is.EqualTo(apiMod2)); + Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2)); Assert.That(mod2, Is.EqualTo(mod3)); - Assert.That(apiMod2, Is.EqualTo(apiMod3)); + Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3)); Assert.That(mod3, Is.EqualTo(mod2)); - Assert.That(apiMod3, Is.EqualTo(apiMod2)); + Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2)); } } } diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 5e552dac09..62f9976c0f 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -16,7 +16,7 @@ using osu.Game.Utils; namespace osu.Game.Online.API { [MessagePackObject] - public class APIMod + public class APIMod : IEquatable { [JsonProperty("acronym")] [Key(0)] @@ -72,8 +72,7 @@ namespace osu.Game.Online.API if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Acronym == other.Acronym && - Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default); + return Acronym == other.Acronym && Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default); } public override string ToString() From f3299737985e1c3b7d3536762a829c7423a6ca05 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 10 Sep 2021 09:37:27 +0700 Subject: [PATCH 114/207] fix supporter promo background colour --- osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index c2b855a0f8..91ff314c15 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -53,6 +53,7 @@ namespace osu.Game.Overlays.Changelog Colour = Color4.Black.Opacity(0.25f), Offset = new Vector2(0, 1), Radius = 3, + Hollow = true, }, Children = new Drawable[] { From 9cf79a80c2235470911297a220057be860eadaa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 12:00:41 +0900 Subject: [PATCH 115/207] Replace many more calls to `CreateAllMods` with more specific calls --- .../Visual/Gameplay/TestSceneAllRulesetPlayers.cs | 3 +-- .../Visual/SongSelect/TestSceneAdvancedStats.cs | 8 ++++---- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 ++-- .../Components/TestSceneTournamentModDisplay.cs | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs index 8487eecdfa..00b5c38e20 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Configuration; @@ -71,7 +70,7 @@ namespace osu.Game.Tests.Visual.Gameplay var working = CreateWorkingBeatmap(rulesetInfo); Beatmap.Value = working; - SelectedMods.Value = new[] { ruleset.CreateAllMods().First(m => m is ModNoFail) }; + SelectedMods.Value = new[] { ruleset.CreateMod() }; Player = CreatePlayer(ruleset); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 5e22bde1d5..dcc2111ad3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select EZ mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - SelectedMods.Value = new[] { ruleset.CreateAllMods().OfType().Single() }; + SelectedMods.Value = new[] { ruleset.CreateMod() }; }); AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select HR mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - SelectedMods.Value = new[] { ruleset.CreateAllMods().OfType().Single() }; + SelectedMods.Value = new[] { ruleset.CreateMod() }; }); AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select unchanged Difficulty Adjust mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - var difficultyAdjustMod = ruleset.CreateAllMods().OfType().Single(); + var difficultyAdjustMod = ruleset.CreateMod(); difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty); SelectedMods.Value = new[] { difficultyAdjustMod }; }); @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select changed Difficulty Adjust mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - var difficultyAdjustMod = ruleset.CreateAllMods().OfType().Single(); + var difficultyAdjustMod = ruleset.CreateMod(); var originalDifficulty = advancedStats.Beatmap.BaseDifficulty; difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 28e5644998..4f7aec3b67 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface var mania = new ManiaRuleset(); testModsWithSameBaseType( - mania.CreateAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)), - mania.CreateAllMods().Single(m => m.GetType() == typeof(ManiaModHidden))); + mania.CreateMod(), + mania.CreateMod()); } [Test] diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index cc1568f429..47e7ed9b61 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components private void success(APIBeatmap apiBeatmap) { beatmap = apiBeatmap.ToBeatmap(rulesets); - var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().CreateAllMods(); + var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods; foreach (var mod in mods) { From 719392de394716856211931284b2fafe7d43c52e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 12:05:10 +0900 Subject: [PATCH 116/207] Change `CreateInstance` to use `Activator.CreateInstance` instead of clone --- osu.Game/Rulesets/Mods/IMod.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index ac17e49e60..ca5053aaca 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -27,6 +27,6 @@ namespace osu.Game.Rulesets.Mods /// /// Create a fresh instance based on this mod. /// - Mod CreateInstance() => ((Mod)this).DeepClone(); + Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType()); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 82707ad382..b0c3836774 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -80,12 +80,7 @@ namespace osu.Game.Rulesets /// The acronym to query for . public Mod CreateModFromAcronym(string acronym) { - var type = AllMods.FirstOrDefault(m => m.Acronym == acronym)?.GetType(); - - if (type != null) - return (Mod)Activator.CreateInstance(type); - - return null; + return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance(); } /// @@ -94,12 +89,7 @@ namespace osu.Game.Rulesets public T CreateMod() where T : Mod { - var type = AllMods.FirstOrDefault(m => m is T)?.GetType(); - - if (type != null) - return (T)Activator.CreateInstance(type); - - return null; + return AllMods.FirstOrDefault(m => m is T)?.CreateInstance() as T; } public abstract IEnumerable GetModsFor(ModType type); From 8acf82944f2f83b84bd9bf0af18d3603bea8f1bb Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 10 Sep 2021 10:17:03 +0700 Subject: [PATCH 117/207] use hex colour directly instead of transparency --- osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index 91ff314c15..d3462c1787 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -53,14 +53,13 @@ namespace osu.Game.Overlays.Changelog Colour = Color4.Black.Opacity(0.25f), Offset = new Vector2(0, 1), Radius = 3, - Hollow = true, }, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.3f), + Colour = Color4Extensions.FromHex("#222027"), }, new Container { From 2838a3a9614ecbf218e719a24444a6015e7e9c69 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 10 Sep 2021 12:25:41 +0900 Subject: [PATCH 118/207] Rewrite conditional to be more 'balanced' --- osu.Game/Users/Drawables/UpdateableAvatar.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index a8726d0cab..24d82c2784 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -69,20 +69,22 @@ namespace osu.Game.Users.Drawables if (user == null && !showGuestOnNull) return null; - if (!openOnClick) + if (openOnClick) + { + return new ClickableAvatar(user) + { + OpenOnClick = true, + ShowUsernameTooltip = showUsernameTooltip, + RelativeSizeAxes = Axes.Both, + }; + } + else { return new DrawableAvatar(user) { RelativeSizeAxes = Axes.Both, }; } - - return new ClickableAvatar(user) - { - OpenOnClick = openOnClick, - ShowUsernameTooltip = showUsernameTooltip, - RelativeSizeAxes = Axes.Both, - }; } } } From c3531e1361bcab20620eaec176792d5118da6f0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 12:42:53 +0900 Subject: [PATCH 119/207] Move more specification from `Mod` to `IMod` --- osu.Game/Rulesets/Mods/IMod.cs | 15 +++++++++++++++ osu.Game/Rulesets/Mods/Mod.cs | 12 ------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index ca5053aaca..d5d1de91de 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -13,6 +13,21 @@ namespace osu.Game.Rulesets.Mods /// string Acronym { get; } + /// + /// The name of this mod. + /// + string Name { get; } + + /// + /// The user readable description of this mod. + /// + string Description { get; } + + /// + /// The type of this mod. + /// + ModType Type { get; } + /// /// The icon of this mod. /// diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index fedee857c3..7136795461 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -22,29 +22,17 @@ namespace osu.Game.Rulesets.Mods [ExcludeFromDynamicCompile] public abstract class Mod : IMod, IEquatable, IDeepCloneable { - /// - /// The name of this mod. - /// [JsonIgnore] public abstract string Name { get; } - /// - /// The shortened name of this mod. - /// public abstract string Acronym { get; } [JsonIgnore] public virtual IconUsage? Icon => null; - /// - /// The type of this mod. - /// [JsonIgnore] public virtual ModType Type => ModType.Fun; - /// - /// The user readable description of this mod. - /// [JsonIgnore] public abstract string Description { get; } From 464797fecf8afdd604d31012f6ff378da49326e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 12:43:12 +0900 Subject: [PATCH 120/207] Allow `ModIcon` to be constructed using an `IMod` --- .../Visual/UserInterface/TestSceneModIcon.cs | 13 +++++++++++++ osu.Game/Rulesets/UI/ModIcon.cs | 10 +++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs index e7fa7d9235..513eb2fafc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; @@ -17,5 +19,16 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime())); AddStep("change mod", () => icon.Mod = new OsuModEasy()); } + + [Test] + public void TestInterfaceModType() + { + ModIcon icon = null; + + var ruleset = new OsuRuleset(); + + AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT"))); + AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ")); + } } } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 725cfa9c26..79bada0490 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null; + public virtual LocalisableString TooltipText => showTooltip ? ((mod as Mod)?.IconTooltip ?? mod.Name) : null; - private Mod mod; + private IMod mod; private readonly bool showTooltip; - public Mod Mod + public IMod Mod { get => mod; set @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.UI /// /// The mod to be displayed /// Whether a tooltip describing the mod should display on hover. - public ModIcon(Mod mod, bool showTooltip = true) + public ModIcon(IMod mod, bool showTooltip = true) { this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); this.showTooltip = showTooltip; @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI updateMod(mod); } - private void updateMod(Mod value) + private void updateMod(IMod value) { modAcronym.Text = value.Acronym; modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question; From 28e93291364cbe78d94aceaee284eb5a488f34db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 12:43:21 +0900 Subject: [PATCH 121/207] Update `LeaderboardModSelector` to avoid creating mod instances --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 6 +++--- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index bf3441d2a0..b4e0e44b2c 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests private readonly BeatmapInfo beatmap; private readonly BeatmapLeaderboardScope scope; private readonly RulesetInfo ruleset; - private readonly IEnumerable mods; + private readonly IEnumerable mods; - public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); @@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests this.beatmap = beatmap; this.scope = scope; this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); - this.mods = mods ?? Array.Empty(); + this.mods = mods ?? Array.Empty(); Success += onSuccess; } diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 2683d7bc6d..6349f115cb 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet { public class LeaderboardModSelector : CompositeDrawable { - public readonly BindableList SelectedMods = new BindableList(); + public readonly BindableList SelectedMods = new BindableList(); public readonly Bindable Ruleset = new Bindable(); private readonly FillFlowContainer modsContainer; @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m.CreateInstance()))); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.ForEach(button => { @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet updateHighlighted(); } - private void selectionChanged(Mod mod, bool selected) + private void selectionChanged(IMod mod, bool selected) { if (selected) SelectedMods.Add(mod); @@ -101,9 +101,9 @@ namespace osu.Game.Overlays.BeatmapSet private const int duration = 200; public readonly BindableBool Highlighted = new BindableBool(); - public Action OnSelectionChanged; + public Action OnSelectionChanged; - public ModButton(Mod mod) + public ModButton(IMod mod) : base(mod) { Scale = new Vector2(0.4f); From 212c3c699c3a3661904c6b502afd49cea327353e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 12:58:12 +0900 Subject: [PATCH 122/207] Reword xmldoc slightly --- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index bf8063cfaf..3658dbab83 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -92,8 +92,8 @@ namespace osu.Game.Beatmaps public BeatmapSetOnlineLanguage Language { get; set; } /// - /// The song ID of this beatmap set. - /// Non-null only if the song is from a featured artist. + /// The track ID of this beatmap set. + /// Non-null only if the track is linked to a featured artist track entry. /// public int? TrackId { get; set; } } From e636692596e8043826a1c461b4f5afd7c53f2589 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 10 Sep 2021 11:00:55 +0700 Subject: [PATCH 123/207] change background to background 5 --- osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index d3462c1787..6780cf4016 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Changelog private readonly FillFlowContainer textContainer; private readonly Container imageContainer; + private readonly Box background; public ChangelogSupporterPromo() { @@ -56,10 +57,9 @@ namespace osu.Game.Overlays.Changelog }, Children = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#222027"), }, new Container { @@ -92,8 +92,10 @@ namespace osu.Game.Overlays.Changelog } [BackgroundDependencyLoader] - private void load(OsuColour colour, TextureStore textures) + private void load(OsuColour colour, TextureStore textures, OverlayColourProvider colourProvider) { + background.Colour = colourProvider.Background5; + SupporterPromoLinkFlowContainer supportLinkText; textContainer.Children = new Drawable[] { From 85b699182e1f9254a45f141988a22bf60898c54d Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 10 Sep 2021 13:01:54 +0900 Subject: [PATCH 124/207] Rename variable to be more descriptive --- osu.Game/Users/Drawables/UpdateableAvatar.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 24d82c2784..6d48104131 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -44,7 +44,7 @@ namespace osu.Game.Users.Drawables protected override double LoadDelay => 200; - private readonly bool openOnClick; + private readonly bool isInteractive; private readonly bool showUsernameTooltip; private readonly bool showGuestOnNull; @@ -52,12 +52,12 @@ namespace osu.Game.Users.Drawables /// Construct a new UpdateableAvatar. /// /// The initial user to display. - /// Whether to open the user's profile when clicked. - /// Whether to show the username rather than "view profile" on the tooltip. + /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile. + /// Whether to show the username rather than "view profile" on the tooltip. (note: this only applies if is also true) /// Whether to show a default guest representation on null user (as opposed to nothing). - public UpdateableAvatar(User user = null, bool openOnClick = true, bool showUsernameTooltip = false, bool showGuestOnNull = true) + public UpdateableAvatar(User user = null, bool isInteractive = true, bool showUsernameTooltip = false, bool showGuestOnNull = true) { - this.openOnClick = openOnClick; + this.isInteractive = isInteractive; this.showUsernameTooltip = showUsernameTooltip; this.showGuestOnNull = showGuestOnNull; @@ -69,7 +69,7 @@ namespace osu.Game.Users.Drawables if (user == null && !showGuestOnNull) return null; - if (openOnClick) + if (isInteractive) { return new ClickableAvatar(user) { From 110f495345176873676462ee07349b9498f5120b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 10 Sep 2021 11:11:55 +0700 Subject: [PATCH 125/207] move internal children to bdl --- .../Changelog/ChangelogSupporterPromo.cs | 148 +++++++++--------- 1 file changed, 71 insertions(+), 77 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index 6780cf4016..1b0a62dc4a 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -26,10 +26,6 @@ namespace osu.Game.Overlays.Changelog private const float image_container_width = 164; private const float heart_size = 75; - private readonly FillFlowContainer textContainer; - private readonly Container imageContainer; - private readonly Box background; - public ChangelogSupporterPromo() { RelativeSizeAxes = Axes.X; @@ -39,6 +35,12 @@ namespace osu.Game.Overlays.Changelog Vertical = 20, Horizontal = 50, }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour, TextureStore textures, OverlayColourProvider colourProvider) + { + SupporterPromoLinkFlowContainer supportLinkText; InternalChildren = new Drawable[] { @@ -57,9 +59,10 @@ namespace osu.Game.Overlays.Changelog }, Children = new Drawable[] { - background = new Box + new Box { RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, }, new Container { @@ -68,7 +71,7 @@ namespace osu.Game.Overlays.Changelog Padding = new MarginPadding { Horizontal = 75 }, Children = new Drawable[] { - textContainer = new FillFlowContainer + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -76,93 +79,84 @@ namespace osu.Game.Overlays.Changelog Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Padding = new MarginPadding { Right = 50 + image_container_width }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = ChangelogStrings.SupportHeading, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light), + Margin = new MarginPadding { Bottom = 20 }, + }, + supportLinkText = new SupporterPromoLinkFlowContainer(t => + { + t.Font = t.Font.With(size: 14); + t.Colour = colour.PinkLighter; + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + new OsuTextFlowContainer(t => + { + t.Font = t.Font.With(size: 12); + t.Colour = colour.PinkLighter; + }) + { + Text = ChangelogStrings.SupportText2.ToString(), + Margin = new MarginPadding { Top = 10 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }, }, - imageContainer = new Container + new Container { RelativeSizeAxes = Axes.Y, Width = image_container_width, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 28 }, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Texture = textures.Get(@"Online/supporter-pippi"), + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(heart_size), + Margin = new MarginPadding { Top = 70 }, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = colour.Pink, + Radius = 10, + Roundness = heart_size / 2, + }, + Child = new Sprite + { + Size = new Vector2(heart_size), + Texture = textures.Get(@"Online/supporter-heart"), + }, + }, + } } } }, } }, }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour, TextureStore textures, OverlayColourProvider colourProvider) - { - background.Colour = colourProvider.Background5; - - SupporterPromoLinkFlowContainer supportLinkText; - textContainer.Children = new Drawable[] - { - new OsuSpriteText - { - Text = ChangelogStrings.SupportHeading, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light), - Margin = new MarginPadding { Bottom = 20 }, - }, - supportLinkText = new SupporterPromoLinkFlowContainer(t => - { - t.Font = t.Font.With(size: 14); - t.Colour = colour.PinkLighter; - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - new OsuTextFlowContainer(t => - { - t.Font = t.Font.With(size: 12); - t.Colour = colour.PinkLighter; - }) - { - Text = ChangelogStrings.SupportText2.ToString(), - Margin = new MarginPadding { Top = 10 }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }; supportLinkText.AddText("Support further development of osu! and "); supportLinkText.AddLink("become an osu!supporter", @"https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold)); supportLinkText.AddText(" today!"); - - imageContainer.Children = new Drawable[] - { - new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Bottom = 28 }, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - Texture = textures.Get(@"Online/supporter-pippi"), - }, - new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(heart_size), - Margin = new MarginPadding { Top = 70 }, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = colour.Pink, - Radius = 10, - Roundness = heart_size / 2, - }, - Child = new Sprite - { - Size = new Vector2(heart_size), - Texture = textures.Get(@"Online/supporter-heart"), - }, - }, - }; } private class SupporterPromoLinkFlowContainer : LinkFlowContainer From 8d1e43423eb5870746db19a4c178d6005e0ffc80 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 10 Sep 2021 14:18:40 +0900 Subject: [PATCH 126/207] Update calls to use new variable name --- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index cf930e985c..f5720cffb0 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Profile.Header Origin = Anchor.CentreLeft, Children = new Drawable[] { - avatar = new UpdateableAvatar(openOnClick: false, showGuestOnNull: false) + avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) { Size = new Vector2(avatar_size), Masking = true, diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 165c095514..5d4430caa2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Toolbar Add(new OpaqueBackground { Depth = 1 }); - Flow.Add(avatar = new UpdateableAvatar(openOnClick: false) + Flow.Add(avatar = new UpdateableAvatar(isInteractive: false) { Masking = true, Size = new Vector2(32), From 84c152e7b6f7d3320a2dcc4f14f15b8485b29430 Mon Sep 17 00:00:00 2001 From: rednir Date: Fri, 10 Sep 2021 08:01:38 +0100 Subject: [PATCH 127/207] break when already found user Co-authored-by: Salman Ahmed --- osu.Game/Online/Chat/ChannelManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 34c6d048a3..3737451140 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -268,8 +268,12 @@ namespace osu.Game.Online.Chat // Check if the user has joined requested channel already. var alreadyJoinedChannel = JoinedChannels.FirstOrDefault( c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase)); + if (alreadyJoinedChannel != null) + { CurrentChannel.Value = alreadyJoinedChannel; + break; + } var request = new GetUserRequest(content); request.Success += OpenPrivateChannel; From 5ec615c7830af66959e2b99b9a36df0b1aebd205 Mon Sep 17 00:00:00 2001 From: rednir Date: Fri, 10 Sep 2021 08:02:15 +0100 Subject: [PATCH 128/207] display user in error message Co-authored-by: Salman Ahmed --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 3737451140..9ecbc40ce2 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -277,7 +277,7 @@ namespace osu.Game.Online.Chat var request = new GetUserRequest(content); request.Success += OpenPrivateChannel; - request.Failure += _ => target.AddNewMessages(new ErrorMessage("User not found.")); + request.Failure += _ => target.AddNewMessages(new ErrorMessage($"User '{content}' was not found.")); api.Queue(request); break; From acb181ff2b6174aa04f0f4b88cc798ea557ad56a Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Fri, 10 Sep 2021 08:15:43 +0100 Subject: [PATCH 129/207] rename `alreadyJoinedChannel` -> `privateChannel` --- osu.Game/Online/Chat/ChannelManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 9ecbc40ce2..347f0af605 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -266,12 +266,12 @@ namespace osu.Game.Online.Chat } // Check if the user has joined requested channel already. - var alreadyJoinedChannel = JoinedChannels.FirstOrDefault( + var privateChannel = JoinedChannels.FirstOrDefault( c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase)); - if (alreadyJoinedChannel != null) + if (privateChannel != null) { - CurrentChannel.Value = alreadyJoinedChannel; + CurrentChannel.Value = privateChannel; break; } From 5a069546656e88d48501d7e4536f9fed42871c1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 18:16:10 +0900 Subject: [PATCH 130/207] Add test coverage of game exit scenario --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index b536233ff0..cc64d37116 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.Play; @@ -388,6 +389,19 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden); } + [Test] + public void TestExitGameFromSongSelect() + { + PushAndConfirm(() => new TestPlaySongSelect()); + exitViaEscapeAndConfirm(); + + pushEscape(); // returns to osu! logo + + AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles); + AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null); + } + private void pushEscape() => AddStep("Press escape", () => InputManager.Key(Key.Escape)); From 94702ee7e3bb1df48a9a07ab63e07bfb059b9f7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 18:10:03 +0900 Subject: [PATCH 131/207] Fix triangles intro attempting to restart track after it is disposed --- osu.Game/Screens/Menu/IntroTriangles.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 36296487a8..a8ca17cec1 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Menu private Sample welcome; private DecoupleableInterpolatingFramedClock decoupledClock; + private TrianglesIntroSequence intro; [BackgroundDependencyLoader] private void load() @@ -66,7 +67,7 @@ namespace osu.Game.Screens.Menu if (UsingThemedIntro) decoupledClock.ChangeSource(Track); - LoadComponentAsync(new TrianglesIntroSequence(logo, background) + LoadComponentAsync(intro = new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, Clock = decoupledClock, @@ -82,6 +83,14 @@ namespace osu.Game.Screens.Menu } } + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + + // important as there is a clock attached to a track which will likely be disposed before returning to this screen. + intro.Expire(); + } + public override void OnResuming(IScreen last) { base.OnResuming(last); From 0e5659acb29789a6cde29fdd0fa84a4f8c4f563d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Sep 2021 14:10:29 +0200 Subject: [PATCH 132/207] Remove leftover exception throw --- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index fac0661a15..2cb696be0a 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -71,7 +71,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 public void OnReleased(GlobalAction action) { - throw new System.NotImplementedException(); } } } From c166f1a06ab09e2309284429f69f9f673ec594d0 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sat, 11 Sep 2021 14:18:09 +0100 Subject: [PATCH 133/207] change error message based on exception message --- osu.Game/Online/Chat/ChannelManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 347f0af605..ffbd34abde 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -277,7 +277,9 @@ namespace osu.Game.Online.Chat var request = new GetUserRequest(content); request.Success += OpenPrivateChannel; - request.Failure += _ => target.AddNewMessages(new ErrorMessage($"User '{content}' was not found.")); + request.Failure += e => target.AddNewMessages( + new ErrorMessage(e.InnerException?.Message == "NotFound" ? $"User '{content}' was not found." : "Could not fetch user.")); + api.Queue(request); break; From 1b264a2dd0ec1a08bfd0662f3e47d792a40b91db Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sat, 11 Sep 2021 16:22:29 +0100 Subject: [PATCH 134/207] make user lookup string public --- osu.Game/Online/API/Requests/GetUserRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index fe954372bf..730e4e02ed 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests { public class GetUserRequest : APIRequest { - private readonly string lookup; + public readonly string Lookup; public readonly RulesetInfo Ruleset; private readonly LookupType lookupType; @@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests /// The ruleset to get the user's info for. public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) { - lookup = userId.ToString(); + Lookup = userId.ToString(); lookupType = LookupType.Id; Ruleset = ruleset; } @@ -38,12 +38,12 @@ namespace osu.Game.Online.API.Requests /// The ruleset to get the user's info for. public GetUserRequest(string username = null, RulesetInfo ruleset = null) { - lookup = username; + Lookup = username; lookupType = LookupType.Username; Ruleset = ruleset; } - protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; private enum LookupType { From 7924a990a346a4f98e5884e4b2d071da6d0b21f6 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sat, 11 Sep 2021 16:22:35 +0100 Subject: [PATCH 135/207] add tests for /chat command --- .../Visual/Online/TestSceneChatOverlay.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 7cfca31167..90263e9deb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -85,6 +86,21 @@ namespace osu.Game.Tests.Visual.Online case JoinChannelRequest joinChannel: joinChannel.TriggerSuccess(); return true; + + case GetUserRequest getUser: + if (getUser.Lookup.Equals("some body", StringComparison.OrdinalIgnoreCase)) + { + getUser.TriggerSuccess(new User() + { + Username = "some body", + Id = 1, + }); + } + else + { + getUser.TriggerFailure(new Exception()); + } + return true; } return false; @@ -322,6 +338,27 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Current channel is channel 1", () => currentChannel == channel1); } + [Test] + public void TestChatCommand() + { + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Open chat with user.", () => channelManager.PostCommand("chat some body")); + AddAssert("PM channel is selected", () => + channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); + + AddStep("Open chat with non-existant user", () => channelManager.PostCommand("chat nobody")); + AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last().GetType() == typeof(ErrorMessage)); + + // Make sure no unnecessary requests are made when the PM chanenl is already open. + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null); + AddStep("Open chat with user.", () => channelManager.PostCommand("chat some body")); + AddAssert("PM channel is selected", () => + channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 605933c46722181edeb4c4280df9cc5df4661f92 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sat, 11 Sep 2021 16:23:17 +0100 Subject: [PATCH 136/207] typo --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 90263e9deb..e546575319 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -351,7 +351,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Open chat with non-existant user", () => channelManager.PostCommand("chat nobody")); AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last().GetType() == typeof(ErrorMessage)); - // Make sure no unnecessary requests are made when the PM chanenl is already open. + // Make sure no unnecessary requests are made when the PM channel is already open. AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null); AddStep("Open chat with user.", () => channelManager.PostCommand("chat some body")); From eeaa8a838076040a0bb1e7f659e83ebbbaad71bd Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sat, 11 Sep 2021 16:47:20 +0100 Subject: [PATCH 137/207] code quality --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index e546575319..9200b5b3dd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online case GetUserRequest getUser: if (getUser.Lookup.Equals("some body", StringComparison.OrdinalIgnoreCase)) { - getUser.TriggerSuccess(new User() + getUser.TriggerSuccess(new User { Username = "some body", Id = 1, @@ -100,6 +100,7 @@ namespace osu.Game.Tests.Visual.Online { getUser.TriggerFailure(new Exception()); } + return true; } From b9c127c07ef27aebaffed87e94457aaa057ce9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Sep 2021 15:54:49 +0200 Subject: [PATCH 138/207] Improve content transitions in beatmap listing --- osu.Game/Overlays/BeatmapListingOverlay.cs | 27 +++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 6861d17f26..935a89b99b 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -75,6 +75,7 @@ namespace osu.Game.Overlays { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Masking = true, Padding = new MarginPadding { Horizontal = 20 }, Children = new Drawable[] { @@ -186,21 +187,16 @@ namespace osu.Game.Overlays if (lastContent != null) { - var transform = lastContent.FadeOut(100, Easing.OutQuint); + lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent || lastContent == supporterRequiredContent) - { - // the placeholders may be used multiple times, so don't expire/dispose them. - transform.Schedule(() => panelTarget.Remove(lastContent)); - } - else - { - // Consider the case when the new content is smaller than the last content. - // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. - // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. - // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. - lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => lastContent.Expire()); - } + // Consider the case when the new content is smaller than the last content. + // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. + // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. + // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. + var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); + + if (lastContent != notFoundContent && lastContent != supporterRequiredContent) + sequence.Then().Schedule(() => lastContent.Expire()); } if (!content.IsAlive) @@ -208,6 +204,9 @@ namespace osu.Game.Overlays content.FadeInFromZero(200, Easing.OutQuint); currentContent = content; + // currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out. + // restore to the initial state. + currentContent.BypassAutoSizeAxes = Axes.None; } protected override void Dispose(bool isDisposing) From b8a6925175cc7b40036d10d1e3d74b12feaf331b Mon Sep 17 00:00:00 2001 From: sh0ckR6 Date: Sat, 11 Sep 2021 17:41:07 -0400 Subject: [PATCH 139/207] Use already-resolved LoungeSubScreen instead of nested delegates --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index bedee39a26..972c7aed47 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.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 osu.Framework.Allocation; using osu.Framework.Audio; @@ -123,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge } } - public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join }; + public Popover GetPopover() => new PasswordEntryPopover(Room) { Lounge = lounge }; public MenuItem[] ContextMenuItems => new MenuItem[] { @@ -179,7 +178,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { private readonly Room room; - public Action, Action> JoinRequested; + public LoungeSubScreen Lounge; public PasswordEntryPopover(Room room) { @@ -220,7 +219,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge }; Child = shakeContainer; - joinButton.Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text, null, joinFailed); + joinButton.Action = () => Lounge?.Join(room, passwordTextbox.Text, null, joinFailed); } private void joinFailed(string error) @@ -239,7 +238,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); - passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text, null, joinFailed); + passwordTextbox.OnCommit += (_, __) => Lounge?.Join(room, passwordTextbox.Text, null, joinFailed); } } } From 6cdc8424527c3c9ccb697f9c9031207d6a3a57bb Mon Sep 17 00:00:00 2001 From: sh0ckR6 Date: Sat, 11 Sep 2021 17:42:49 -0400 Subject: [PATCH 140/207] Remove placeholder text response Weird UX, doesn't feel right compared to the rest of lazer --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 972c7aed47..8cf34cd9f3 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -225,8 +225,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private void joinFailed(string error) { passwordTextbox.Text = string.Empty; - passwordTextbox.PlaceholderText = "incorrect password"; - passwordTextbox.Colour = Color4.Red; shakeContainer.OnShakeFinish += () => passwordTextbox.Colour = Color4.White; From e018071be47e31457c6969d0cbac618e37932b3a Mon Sep 17 00:00:00 2001 From: sh0ckR6 Date: Sat, 11 Sep 2021 19:50:41 -0400 Subject: [PATCH 141/207] Remove OnShakeFinish event --- osu.Game/Graphics/Containers/ShakeContainer.cs | 7 ------- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 -- 2 files changed, 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs index ffa8ef585e..dca9df1e98 100644 --- a/osu.Game/Graphics/Containers/ShakeContainer.cs +++ b/osu.Game/Graphics/Containers/ShakeContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,11 +26,6 @@ namespace osu.Game.Graphics.Containers /// public float ShakeMagnitude = 8; - /// - /// Fired when finishes - /// - public event Action OnShakeFinish; - /// /// Shake the contents of this container. /// @@ -56,7 +50,6 @@ namespace osu.Game.Graphics.Containers } sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine); - sequence.Finally(_ => OnShakeFinish?.Invoke()); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 8cf34cd9f3..db1566999e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -226,8 +226,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { passwordTextbox.Text = string.Empty; - shakeContainer.OnShakeFinish += () => passwordTextbox.Colour = Color4.White; - shakeContainer.Shake(); } From e511c2ef2b0be57fbdc4ffb8d899eaa3ddac891a Mon Sep 17 00:00:00 2001 From: rednir Date: Sun, 12 Sep 2021 08:50:53 +0100 Subject: [PATCH 142/207] add comment --- osu.Game/Online/Chat/ChannelManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index ffbd34abde..853c28c7c8 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -265,7 +265,8 @@ namespace osu.Game.Online.Chat break; } - // Check if the user has joined requested channel already. + // Check if the user has joined the requested channel already. + // This uses the channel name for comparison as the PM user's username is unavailable after a restart. var privateChannel = JoinedChannels.FirstOrDefault( c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase)); From c4627bed6dbf713369273026f2ee946ad68895af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 12:56:36 +0200 Subject: [PATCH 143/207] Print username in case of generic network failure too --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 853c28c7c8..47d5955fb0 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -279,7 +279,7 @@ namespace osu.Game.Online.Chat var request = new GetUserRequest(content); request.Success += OpenPrivateChannel; request.Failure += e => target.AddNewMessages( - new ErrorMessage(e.InnerException?.Message == "NotFound" ? $"User '{content}' was not found." : "Could not fetch user.")); + new ErrorMessage(e.InnerException?.Message == @"NotFound" ? $"User '{content}' was not found." : $"Could not fetch user '{content}'.")); api.Queue(request); break; From 3467b1f60c9d3f3c3fae1c75cda9702316fec4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 13:00:52 +0200 Subject: [PATCH 144/207] Retouch chat command test slightly --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 9200b5b3dd..609e637914 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -345,17 +345,17 @@ namespace osu.Game.Tests.Visual.Online AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Open chat with user.", () => channelManager.PostCommand("chat some body")); + AddStep("Open chat with user", () => channelManager.PostCommand("chat some body")); AddAssert("PM channel is selected", () => channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); - AddStep("Open chat with non-existant user", () => channelManager.PostCommand("chat nobody")); - AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last().GetType() == typeof(ErrorMessage)); + AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat nobody")); + AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last() is ErrorMessage); // Make sure no unnecessary requests are made when the PM channel is already open. AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null); - AddStep("Open chat with user.", () => channelManager.PostCommand("chat some body")); + AddStep("Open chat with user", () => channelManager.PostCommand("chat some body")); AddAssert("PM channel is selected", () => channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); } From 8357efc74fbaaf0e244ab8ce52d4a44e41995d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Sep 2021 20:43:17 +0200 Subject: [PATCH 145/207] Make `EditorTestScene` go through `EditorLoader` --- osu.Game/Screens/Edit/EditorLoader.cs | 28 +++++++++++---------- osu.Game/Tests/Visual/EditorTestScene.cs | 31 +++++++++++++++++++++--- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index aec7d32939..6bbfa92c3b 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -34,6 +34,20 @@ namespace osu.Game.Screens.Edit [CanBeNull] private ScheduledDelegate scheduledDifficultySwitch; + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + new LoadingSpinner(true) + { + State = { Value = Visibility.Visible }, + } + }); + } + + protected virtual Editor CreateEditor() => new Editor(this); + protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -47,18 +61,6 @@ namespace osu.Game.Screens.Edit } } - [BackgroundDependencyLoader] - private void load() - { - AddRangeInternal(new Drawable[] - { - new LoadingSpinner(true) - { - State = { Value = Visibility.Visible }, - } - }); - } - public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) { scheduledDifficultySwitch?.Cancel(); @@ -81,7 +83,7 @@ namespace osu.Game.Screens.Edit private void pushEditor() { - this.Push(new Editor(this)); + this.Push(CreateEditor()); ValidForResume = false; } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index a393802309..9fb9df39a8 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Menu; using osu.Game.Skinning; namespace osu.Game.Tests.Visual @@ -24,7 +25,9 @@ namespace osu.Game.Tests.Visual { protected EditorBeatmap EditorBeatmap; - protected TestEditor Editor { get; private set; } + private TestEditorLoader editorLoader; + + protected TestEditor Editor => editorLoader.Editor; protected EditorClock EditorClock { get; private set; } @@ -33,9 +36,19 @@ namespace osu.Game.Tests.Visual /// protected virtual bool IsolateSavingFromDatabase => true; + // required for screen transitions to work properly + // (see comment in EditorLoader.LogoArriving). + [Cached] + private OsuLogo logo = new OsuLogo + { + Alpha = 0 + }; + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { + Add(logo); + var working = CreateWorkingBeatmap(Ruleset.Value); Beatmap.Value = working; @@ -59,7 +72,7 @@ namespace osu.Game.Tests.Visual protected virtual void LoadEditor() { - LoadScreen(Editor = CreateEditor()); + LoadScreen(editorLoader = new TestEditorLoader()); } /// @@ -70,7 +83,14 @@ namespace osu.Game.Tests.Visual protected sealed override Ruleset CreateRuleset() => CreateEditorRuleset(); - protected virtual TestEditor CreateEditor() => new TestEditor(); + protected class TestEditorLoader : EditorLoader + { + public TestEditor Editor { get; private set; } + + protected sealed override Editor CreateEditor() => Editor = CreateTestEditor(this); + + protected virtual TestEditor CreateTestEditor(EditorLoader loader) => new TestEditor(loader); + } protected class TestEditor : Editor { @@ -87,6 +107,11 @@ namespace osu.Game.Tests.Visual public new void Paste() => base.Paste(); public new bool HasUnsavedChanges => base.HasUnsavedChanges; + + public TestEditor(EditorLoader loader = null) + : base(loader) + { + } } private class TestBeatmapManager : BeatmapManager From 5ae2f419304d95505308888f10a64e5a867e699d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Sep 2021 21:07:34 +0200 Subject: [PATCH 146/207] Make difficulty switching test scene use `EditorTestScene` --- .../Editing/TestSceneDifficultySwitching.cs | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 0f3d413a7d..587c37c6f5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -9,26 +9,20 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Dialog; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.Menu; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public class TestSceneDifficultySwitching : ScreenTestScene + public class TestSceneDifficultySwitching : EditorTestScene { - private BeatmapSetInfo importedBeatmapSet; - private Editor editor; + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - // required for screen transitions to work properly - // (see comment in EditorLoader.LogoArriving). - [Cached] - private OsuLogo logo = new OsuLogo - { - Alpha = 0 - }; + protected override bool IsolateSavingFromDatabase => false; [Resolved] private OsuGameBase game { get; set; } @@ -36,20 +30,18 @@ namespace osu.Game.Tests.Visual.Editing [Resolved] private BeatmapManager beatmaps { get; set; } - [BackgroundDependencyLoader] - private void load() => Add(logo); + private BeatmapSetInfo importedBeatmapSet; - [SetUpSteps] - public void SetUp() + public override void SetUpSteps() { AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result); + base.SetUpSteps(); + } - AddStep("set current beatmap", () => Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First())); - AddStep("push loader", () => Stack.Push(new EditorLoader())); - - AddUntilStep("wait for editor push", () => Stack.CurrentScreen is Editor); - AddStep("store editor", () => editor = (Editor)Stack.CurrentScreen); - AddUntilStep("wait for editor to load", () => editor.IsLoaded); + protected override void LoadEditor() + { + Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); + base.LoadEditor(); } [Test] @@ -72,11 +64,7 @@ namespace osu.Game.Tests.Visual.Editing BeatmapInfo targetDifficulty = null; PromptForSaveDialog saveDialog = null; - AddStep("remove first hitobject", () => - { - var editorBeatmap = editor.ChildrenOfType().Single(); - editorBeatmap.RemoveAt(0); - }); + AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0)); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); switchToDifficulty(() => targetDifficulty); @@ -105,11 +93,7 @@ namespace osu.Game.Tests.Visual.Editing BeatmapInfo targetDifficulty = null; PromptForSaveDialog saveDialog = null; - AddStep("remove first hitobject", () => - { - var editorBeatmap = editor.ChildrenOfType().Single(); - editorBeatmap.RemoveAt(0); - }); + AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0)); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); switchToDifficulty(() => targetDifficulty); @@ -134,10 +118,10 @@ namespace osu.Game.Tests.Visual.Editing private void switchToDifficulty(Func difficulty) { - AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any()); + AddUntilStep("wait for menubar to load", () => Editor.ChildrenOfType().Any()); AddStep("open file menu", () => { - var menuBar = editor.ChildrenOfType().Single(); + var menuBar = Editor.ChildrenOfType().Single(); var fileMenu = menuBar.ChildrenOfType().First(); InputManager.MoveMouseTo(fileMenu); InputManager.Click(MouseButton.Left); @@ -146,7 +130,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("open difficulty menu", () => { var difficultySelector = - editor.ChildrenOfType().Single(item => item.Item.Text.Value.ToString().Contains("Change difficulty")); + Editor.ChildrenOfType().Single(item => item.Item.Text.Value.ToString().Contains("Change difficulty")); InputManager.MoveMouseTo(difficultySelector); }); AddWaitStep("wait for open", 3); @@ -154,7 +138,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("switch to target difficulty", () => { var difficultyMenuItem = - editor.ChildrenOfType() + Editor.ChildrenOfType() .Last(item => item.Item is DifficultyMenuItem difficultyItem && difficultyItem.Beatmap.Equals(difficulty.Invoke())); InputManager.MoveMouseTo(difficultyMenuItem); InputManager.Click(MouseButton.Left); From f8a681d810b2fc7c582a7941480bb11f50b8d977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 13:40:06 +0200 Subject: [PATCH 147/207] Delegate `Editor{Beatmap,Clock}` to `Editor` directly --- osu.Game/Tests/Visual/EditorTestScene.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 9fb9df39a8..3bfffeb00e 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -23,13 +23,12 @@ namespace osu.Game.Tests.Visual { public abstract class EditorTestScene : ScreenTestScene { - protected EditorBeatmap EditorBeatmap; - private TestEditorLoader editorLoader; protected TestEditor Editor => editorLoader.Editor; - protected EditorClock EditorClock { get; private set; } + protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single(); + protected EditorClock EditorClock => Editor.ChildrenOfType().Single(); /// /// Whether any saves performed by the editor should be isolate (and not persist) to the underlying . @@ -66,8 +65,6 @@ namespace osu.Game.Tests.Visual AddStep("load editor", LoadEditor); AddUntilStep("wait for editor to load", () => EditorComponentsReady); - AddStep("get beatmap", () => EditorBeatmap = Editor.ChildrenOfType().Single()); - AddStep("get clock", () => EditorClock = Editor.ChildrenOfType().Single()); } protected virtual void LoadEditor() From 22fa9a303e469cc8bea35e57e187adc9cf0fd192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 13:55:48 +0200 Subject: [PATCH 148/207] Expose test helper for switching between difficulties --- .../Editing/TestSceneDifficultySwitching.cs | 32 +------------------ osu.Game/Screens/Edit/Editor.cs | 4 +-- osu.Game/Tests/Visual/EditorTestScene.cs | 2 ++ 3 files changed, 5 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 587c37c6f5..a439555fde 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -7,14 +7,11 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Tests.Beatmaps.IO; -using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -116,34 +113,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } - private void switchToDifficulty(Func difficulty) - { - AddUntilStep("wait for menubar to load", () => Editor.ChildrenOfType().Any()); - AddStep("open file menu", () => - { - var menuBar = Editor.ChildrenOfType().Single(); - var fileMenu = menuBar.ChildrenOfType().First(); - InputManager.MoveMouseTo(fileMenu); - InputManager.Click(MouseButton.Left); - }); - - AddStep("open difficulty menu", () => - { - var difficultySelector = - Editor.ChildrenOfType().Single(item => item.Item.Text.Value.ToString().Contains("Change difficulty")); - InputManager.MoveMouseTo(difficultySelector); - }); - AddWaitStep("wait for open", 3); - - AddStep("switch to target difficulty", () => - { - var difficultyMenuItem = - Editor.ChildrenOfType() - .Last(item => item.Item is DifficultyMenuItem difficultyItem && difficultyItem.Beatmap.Equals(difficulty.Invoke())); - InputManager.MoveMouseTo(difficultyMenuItem); - InputManager.Click(MouseButton.Left); - }); - } + private void switchToDifficulty(Func difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke())); private void confirmEditingBeatmap(Func targetDifficulty) { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1b9a94da58..28ae7e620e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -737,10 +737,10 @@ namespace osu.Game.Screens.Edit private DifficultyMenuItem createDifficultyMenuItem(BeatmapInfo beatmapInfo) { bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmapInfo); - return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, switchToDifficulty); + return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); } - private void switchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo); + protected void SwitchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo); private void cancelExit() => loader?.CancelPendingDifficultySwitch(); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 3bfffeb00e..1e26036116 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -103,6 +103,8 @@ namespace osu.Game.Tests.Visual public new void Paste() => base.Paste(); + public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo); + public new bool HasUnsavedChanges => base.HasUnsavedChanges; public TestEditor(EditorLoader loader = null) From eae5d62fa5e0c9b372cdfe4259a77cefcfe7fa7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 15:50:41 +0200 Subject: [PATCH 149/207] Store editor beatmap locally before editor exit --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b6ae91844a..440d66ff9f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -11,6 +11,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; using osu.Game.Tests.Resources; using SharpCompress.Archives; @@ -55,6 +56,9 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestExitWithoutSave() { + EditorBeatmap editorBeatmap = null; + + AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap); AddStep("exit without save", () => { Editor.Exit(); @@ -62,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing }); AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); - AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); } [Test] From 925b455330b923d6f6059337e9ccb006be270c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 16:40:52 +0200 Subject: [PATCH 150/207] Add "samples match playback rate" setting to beatmap info --- osu.Game/Beatmaps/BeatmapInfo.cs | 5 + ...11_AddSamplesMatchPlaybackRate.Designer.cs | 515 ++++++++++++++++++ ...10912144011_AddSamplesMatchPlaybackRate.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 2 + 4 files changed, 545 insertions(+) create mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs create mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3eb766a667..7dd1dd2cf4 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -93,6 +93,11 @@ namespace osu.Game.Beatmaps public bool WidescreenStoryboard { get; set; } public bool EpilepsyWarning { get; set; } + /// + /// Whether or not sound samples should change rate when playing with speed-changing mods. + /// + public bool SamplesMatchPlaybackRate { get; set; } + public CountdownType Countdown { get; set; } = CountdownType.Normal; /// diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs new file mode 100644 index 0000000000..6e53d7fae0 --- /dev/null +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs @@ -0,0 +1,515 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210912144011_AddSamplesMatchPlaybackRate")] + partial class AddSamplesMatchPlaybackRate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("CountdownOffset"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SamplesMatchPlaybackRate"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs new file mode 100644 index 0000000000..bf3f855d5f --- /dev/null +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSamplesMatchPlaybackRate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SamplesMatchPlaybackRate", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SamplesMatchPlaybackRate", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 470907ada6..036c26cb0a 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -81,6 +81,8 @@ namespace osu.Game.Migrations b.Property("RulesetID"); + b.Property("SamplesMatchPlaybackRate"); + b.Property("SpecialStyle"); b.Property("StackLeniency"); From cd181452be90a7fc96a888bfac732fca7e921615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 16:45:27 +0200 Subject: [PATCH 151/207] Add decoding support for `SamplesMatchPlaybackRate` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 1 + osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 8560a36fb4..a4bf8c92e3 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmapInfo.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); + Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index accefb2583..4b5eaafa4a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -180,6 +180,10 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; break; + case @"SamplesMatchPlaybackRate": + beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1; + break; + case @"Countdown": beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value); break; From af7c2b93e63b78d3a119ec8580432b02b7211765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 16:47:38 +0200 Subject: [PATCH 152/207] Add encoding support for `SamplesMatchPlaybackRate` --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 75d9a56f3e..aef13b8872 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -105,8 +105,8 @@ namespace osu.Game.Beatmaps.Formats if (beatmap.BeatmapInfo.RulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); - // if (b.SamplesMatchPlaybackRate) - // writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); + if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) + writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); } private void handleEditor(TextWriter writer) From 345cde251db4b35d0308083a37255dc7bff24c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 16:54:17 +0200 Subject: [PATCH 153/207] Add "samples match playback rate" to editor setup screen --- osu.Game/Screens/Edit/Setup/DesignSection.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 90f95a668e..d5d93db050 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Setup private LabelledSwitchButton widescreenSupport; private LabelledSwitchButton epilepsyWarning; private LabelledSwitchButton letterboxDuringBreaks; + private LabelledSwitchButton samplesMatchPlaybackRate; public override LocalisableString Title => "Design"; @@ -79,6 +80,12 @@ namespace osu.Game.Screens.Edit.Setup Label = "Letterbox during breaks", Description = "Adds horizontal letterboxing to give a cinematic look during breaks.", Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks } + }, + samplesMatchPlaybackRate = new LabelledSwitchButton + { + Label = "Samples match playback rate", + Description = "When enabled, all samples will speed up or slow down when rate-changing mods are enabled.", + Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate } } }; } @@ -96,6 +103,7 @@ namespace osu.Game.Screens.Edit.Setup widescreenSupport.Current.BindValueChanged(_ => updateBeatmap()); epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap()); letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap()); + samplesMatchPlaybackRate.Current.BindValueChanged(_ => updateBeatmap()); } private void updateCountdownSettingsVisibility() => CountdownSettings.FadeTo(EnableCountdown.Current.Value ? 1 : 0); @@ -115,6 +123,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; + Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; } } } From 1be8cb452f87afd29db37fec2b8b68e5fb296f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 16:57:21 +0200 Subject: [PATCH 154/207] Make new beatmaps' samples match playback rate by default --- osu.Game/Beatmaps/BeatmapManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 27aa874dc9..bd85017d58 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -129,6 +129,7 @@ namespace osu.Game.Beatmaps Ruleset = ruleset, Metadata = metadata, WidescreenStoryboard = true, + SamplesMatchPlaybackRate = true, } } }; From fdd48c3e71cf997e3acfe626a9f0ac40f7281d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 17:41:50 +0200 Subject: [PATCH 155/207] Refactor note colouring test scene --- .../TestSceneTimingBasedNoteColouring.cs | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index e14ad92842..8405b14e1f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Framework.Bindables; +using osu.Framework.Testing; namespace osu.Game.Rulesets.Mania.Tests { @@ -22,14 +23,42 @@ namespace osu.Game.Rulesets.Mania.Tests [Resolved] private RulesetConfigCache configCache { get; set; } - private readonly Bindable configTimingBasedNoteColouring = new Bindable(); + private Bindable configTimingBasedNoteColouring; - protected override void LoadComplete() + private ManualClock clock; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("setup hierarchy", () => Child = new Container + { + Clock = new FramedClock(clock = new ManualClock()), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new[] + { + Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap()) + } + }); + AddStep("retrieve config bindable", () => + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + configTimingBasedNoteColouring = config.GetBindable(ManiaRulesetSetting.TimingBasedNoteColouring); + }); + } + + [Test] + public void TestSimple() + { + AddStep("enable", () => configTimingBasedNoteColouring.Value = true); + AddStep("disable", () => configTimingBasedNoteColouring.Value = false); + } + + private ManiaBeatmap createTestBeatmap() { const double beat_length = 500; - var ruleset = new ManiaRuleset(); - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }) { HitObjects = @@ -45,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests new Note { StartTime = beat_length } }, ControlPointInfo = new ControlPointInfo(), - BeatmapInfo = { Ruleset = ruleset.RulesetInfo }, + BeatmapInfo = { Ruleset = Ruleset.Value }, }; foreach (var note in beatmap.HitObjects) @@ -57,24 +86,7 @@ namespace osu.Game.Rulesets.Mania.Tests { BeatLength = beat_length }); - - Child = new Container - { - Clock = new FramedClock(new ManualClock()), - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new[] - { - ruleset.CreateDrawableRulesetWith(beatmap) - } - }; - - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring); - - AddStep("Enable", () => configTimingBasedNoteColouring.Value = true); - AddStep("Disable", () => configTimingBasedNoteColouring.Value = false); + return beatmap; } } } From 1edf608260c350ffe3164ea99be484ef15609fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 17:48:08 +0200 Subject: [PATCH 156/207] Add failing test case --- .../TestSceneTimingBasedNoteColouring.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index 8405b14e1f..449a6ff23d 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Allocation; @@ -14,6 +15,9 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Framework.Bindables; using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Tests { @@ -26,6 +30,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Bindable configTimingBasedNoteColouring; private ManualClock clock; + private DrawableManiaRuleset drawableRuleset; [SetUpSteps] public void SetUpSteps() @@ -38,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Tests Origin = Anchor.Centre, Children = new[] { - Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap()) + drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap()) } }); AddStep("retrieve config bindable", () => @@ -55,6 +60,28 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("disable", () => configTimingBasedNoteColouring.Value = false); } + [Test] + public void TestToggleOffScreen() + { + AddStep("enable", () => configTimingBasedNoteColouring.Value = true); + + seekTo(10000); + AddStep("disable", () => configTimingBasedNoteColouring.Value = false); + seekTo(0); + AddAssert("all notes not coloured", () => this.ChildrenOfType().All(note => note.Colour == Colour4.White)); + + seekTo(10000); + AddStep("enable again", () => configTimingBasedNoteColouring.Value = true); + seekTo(0); + AddAssert("some notes coloured", () => this.ChildrenOfType().Any(note => note.Colour != Colour4.White)); + } + + private void seekTo(double time) + { + AddStep($"seek to {time}", () => clock.CurrentTime = time); + AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1)); + } + private ManiaBeatmap createTestBeatmap() { const double beat_length = 500; From 922fa96d411d6119a24020243785d8d912d8ea1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 17:51:15 +0200 Subject: [PATCH 157/207] Fix notes not updating snap colour on application --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 33d872dfb6..d53c28868d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true); } + protected override void OnApply() + { + base.OnApply(); + updateSnapColour(); + } + protected override void OnDirectionChanged(ValueChangedEvent e) { base.OnDirectionChanged(e); From 447001931cf67b3896c3934d1c23209b2be3ddc7 Mon Sep 17 00:00:00 2001 From: sh0ckR6 Date: Sun, 12 Sep 2021 14:36:11 -0400 Subject: [PATCH 158/207] Resolve LoungeSubScreen from PasswordEntryPopover This is preferred over passing down the already-resolved LoungeSubScreen --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index db1566999e..bec0ea419c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge } } - public Popover GetPopover() => new PasswordEntryPopover(Room) { Lounge = lounge }; + public Popover GetPopover() => new PasswordEntryPopover(Room); public MenuItem[] ContextMenuItems => new MenuItem[] { @@ -178,7 +178,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { private readonly Room room; - public LoungeSubScreen Lounge; + [Resolved(canBeNull: true)] + private LoungeSubScreen lounge { get; set; } public PasswordEntryPopover(Room room) { @@ -219,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge }; Child = shakeContainer; - joinButton.Action = () => Lounge?.Join(room, passwordTextbox.Text, null, joinFailed); + joinButton.Action = () => lounge?.Join(room, passwordTextbox.Text, null, joinFailed); } private void joinFailed(string error) @@ -234,7 +235,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); - passwordTextbox.OnCommit += (_, __) => Lounge?.Join(room, passwordTextbox.Text, null, joinFailed); + passwordTextbox.OnCommit += (_, __) => lounge?.Join(room, passwordTextbox.Text, null, joinFailed); } } } From 5969e2b8529e7917f86770bfe1d4e692e6d58751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 13 Sep 2021 00:13:07 +0200 Subject: [PATCH 159/207] Add TODO comment about lack of in-gameplay support --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 7dd1dd2cf4..8cb5da8083 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -95,6 +95,7 @@ namespace osu.Game.Beatmaps /// /// Whether or not sound samples should change rate when playing with speed-changing mods. + /// TODO: only read/write supported for now, requires implementation in gameplay. /// public bool SamplesMatchPlaybackRate { get; set; } From 7fe0eefb789a52d47a19286db53dacc4a2b2eaaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Sep 2021 14:12:18 +0900 Subject: [PATCH 160/207] Add inline comment regarding team switch sample logic Feels a bit convoluted without this. Don't really have a better suggestion for now so a comment will do. --- .../Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 915cf30963..833fbd6605 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -105,6 +105,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants if (newTeam == displayedTeam) return; + // only play the sample if an already valid team changes to another valid team. + // this avoids playing a sound for each user if the match type is changed to/from a team mode. if (newTeam != null && displayedTeam != null) sampleTeamSwap?.Play(); From 684c39dad04a87d0cbaa7cbb341b9bb5133fec9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Sep 2021 14:12:37 +0900 Subject: [PATCH 161/207] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7378450c38..d4331a5e65 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d80dd075ee..941656bb70 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8ce757974e..73e0030114 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 52c69d2f2290815ee5406079423849ed6eaf6961 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Sep 2021 14:17:45 +0900 Subject: [PATCH 162/207] Adjust value to not be full width (but allow for more accommodations with localised versions) --- .../Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 2e49671669..fae0318359 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Text = InputSettingsStrings.ResetSectionButton; RelativeSizeAxes = Axes.X; - Width = Content.Width; + Width = 0.8f; Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; Margin = new MarginPadding { Top = 15 }; From caf7ef6519bb928f44769fbddbb862aa1fd40809 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Sep 2021 15:00:33 +0900 Subject: [PATCH 163/207] Add missing screen level mod application settings for some screens Closes #7480. But based on discussion in there this solution may change. --- .../Settings/Sections/Maintenance/DirectorySelectScreen.cs | 2 ++ osu.Game/Screens/Import/FileImportScreen.cs | 2 ++ osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index e509cac2f1..1d67968ab1 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private OsuDirectorySelector directorySelector; + public override bool AllowTrackAdjustments => false; + /// /// Text to display in the header to inform the user of what they are selecting. /// diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs index 7e1d55b3e2..606174193d 100644 --- a/osu.Game/Screens/Import/FileImportScreen.cs +++ b/osu.Game/Screens/Import/FileImportScreen.cs @@ -23,6 +23,8 @@ namespace osu.Game.Screens.Import { public override bool HideOverlaysOnEnter => true; + public override bool AllowTrackAdjustments => false; + private OsuFileSelector fileSelector; private Container contentContainer; private TextFlowContainer currentFileText; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index fc20b21b60..62bfd2cfed 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.OnlinePlay [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + public override bool AllowTrackAdjustments => false; + public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; // this is required due to PlayerLoader eventually being pushed to the main stack From 0f5ed81a7a51467b1c10556856bc602ecda0622a Mon Sep 17 00:00:00 2001 From: AbstractQbit <38468635+AbstractQbit@users.noreply.github.com> Date: Mon, 13 Sep 2021 13:31:13 +0300 Subject: [PATCH 164/207] Fix dimmed checked nub artifact This adds transition that extends nub's border to fill it. Fill fade can be removed, but combined effect looks nicer imo, and the fill is still needed because if removed, border becomes invisible for some reason. --- osu.Game/Graphics/UserInterface/Nub.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 664f32b083..4e5c78be77 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -47,6 +47,7 @@ namespace osu.Game.Graphics.UserInterface }; Current.ValueChanged += filled => fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint); + Current.ValueChanged += filled => this.TransformTo(nameof(BorderThickness), filled.NewValue ? 7 : border_width, 200, Easing.OutQuint); } [BackgroundDependencyLoader] From 4d2373ffb9e8216394936b31a92b9fd72c7788e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Sep 2021 20:08:46 +0900 Subject: [PATCH 165/207] Combine similar value changed calls --- osu.Game/Graphics/UserInterface/Nub.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 4e5c78be77..bd3b2e1715 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -46,8 +46,11 @@ namespace osu.Game.Graphics.UserInterface }, }; - Current.ValueChanged += filled => fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint); - Current.ValueChanged += filled => this.TransformTo(nameof(BorderThickness), filled.NewValue ? 7 : border_width, 200, Easing.OutQuint); + Current.ValueChanged += filled => + { + fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint); + this.TransformTo(nameof(BorderThickness), filled.NewValue ? 7 : border_width, 200, Easing.OutQuint); + }; } [BackgroundDependencyLoader] From 9c1fc2ec6536e5b1ac363d8717e224560609f370 Mon Sep 17 00:00:00 2001 From: AbstractQbit <38468635+AbstractQbit@users.noreply.github.com> Date: Mon, 13 Sep 2021 14:19:33 +0300 Subject: [PATCH 166/207] Tweak filled nub border width value --- osu.Game/Graphics/UserInterface/Nub.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index bd3b2e1715..6807d007bb 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.UserInterface Current.ValueChanged += filled => { fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint); - this.TransformTo(nameof(BorderThickness), filled.NewValue ? 7 : border_width, 200, Easing.OutQuint); + this.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint); }; } From 7267602b951db6c708d379331bf7173528615c24 Mon Sep 17 00:00:00 2001 From: AbstractQbit <38468635+AbstractQbit@users.noreply.github.com> Date: Mon, 13 Sep 2021 17:14:39 +0300 Subject: [PATCH 167/207] Fix icon orientation for horizontal bar hit error meter --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 20 +++++++++++++------ osu.Game/Skinning/LegacySkin.cs | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 89f61785e8..a557e0acd7 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -33,6 +33,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private const float chevron_size = 8; private SpriteIcon arrow; + private SpriteIcon iconEarly; + private SpriteIcon iconLate; private Container colourBarsEarly; private Container colourBarsLate; @@ -97,25 +99,21 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativeSizeAxes = Axes.Both, Height = 0.5f, }, - new SpriteIcon + iconEarly = new SpriteIcon { Y = -10, Size = new Vector2(10), Icon = FontAwesome.Solid.ShippingFast, Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - // undo any layout rotation to display the icon the correct orientation - Rotation = -Rotation, }, - new SpriteIcon + iconLate = new SpriteIcon { Y = 10, Size = new Vector2(10), Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, - // undo any layout rotation to display the icon the correct orientation - Rotation = -Rotation, } } }, @@ -130,6 +128,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters }; createColourBars(colours); + OrientIcons(); } protected override void LoadComplete() @@ -143,6 +142,15 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters arrow.Delay(200).FadeInFromZero(600); } + /// + /// Method to undo any layout rotation to display icons in the correct orientation. + /// + public void OrientIcons() + { + iconEarly.Rotation = -Rotation; + iconLate.Rotation = -Rotation; + } + private void createColourBars(OsuColour colours) { var windows = HitWindows.GetAllAvailableWindows().ToArray(); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b09620411b..41f5b7ae46 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -376,6 +376,7 @@ namespace osu.Game.Skinning hitError.Anchor = Anchor.BottomCentre; hitError.Origin = Anchor.CentreLeft; hitError.Rotation = -90; + if (hitError is BarHitErrorMeter barHitError) barHitError.OrientIcons(); } if (songProgress != null) From 3c75094f4350181f3e1e9a5dc070231a94906280 Mon Sep 17 00:00:00 2001 From: AbstractQbit <38468635+AbstractQbit@users.noreply.github.com> Date: Mon, 13 Sep 2021 19:41:55 +0300 Subject: [PATCH 168/207] Move `BarHitErrorMeter`'s icon reorintation to `Update()` --- .../Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 9 ++++----- osu.Game/Skinning/LegacySkin.cs | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index a557e0acd7..7562df5a3b 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -128,7 +128,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters }; createColourBars(colours); - OrientIcons(); } protected override void LoadComplete() @@ -142,11 +141,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters arrow.Delay(200).FadeInFromZero(600); } - /// - /// Method to undo any layout rotation to display icons in the correct orientation. - /// - public void OrientIcons() + protected override void Update() { + base.Update(); + + // undo any layout rotation to display icons in the correct orientation iconEarly.Rotation = -Rotation; iconLate.Rotation = -Rotation; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 41f5b7ae46..b09620411b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -376,7 +376,6 @@ namespace osu.Game.Skinning hitError.Anchor = Anchor.BottomCentre; hitError.Origin = Anchor.CentreLeft; hitError.Rotation = -90; - if (hitError is BarHitErrorMeter barHitError) barHitError.OrientIcons(); } if (songProgress != null) From 80e54d51f2bfe0e367bdd7f65f4a793185bafcd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Sep 2021 16:28:46 +0200 Subject: [PATCH 169/207] Add failing test for preserving editor clock time --- .../Editing/TestSceneDifficultySwitching.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index a439555fde..0954bf1b49 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -55,6 +55,22 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } + [Test] + public void TestClockPositionPreservedBetweenSwitches() + { + BeatmapInfo targetDifficulty = null; + AddStep("seek editor to 00:05:00", () => EditorClock.Seek(5000)); + + AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); + switchToDifficulty(() => targetDifficulty); + confirmEditingBeatmap(() => targetDifficulty); + AddAssert("editor clock at 00:05:00", () => EditorClock.CurrentTime == 5000); + + AddStep("exit editor", () => Stack.Exit()); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + [Test] public void TestPreventSwitchDueToUnsavedChanges() { @@ -118,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing private void confirmEditingBeatmap(Func targetDifficulty) { AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke())); - AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen == Editor && Editor?.IsLoaded == true); } } } From 3fc72271f103918a622d509d86a0fbad01172685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Sep 2021 18:36:57 +0200 Subject: [PATCH 170/207] Restore editor clock time after difficulty switch --- osu.Game/Screens/Edit/Editor.cs | 13 ++++++++++++- osu.Game/Screens/Edit/EditorLoader.cs | 12 ++++++++++-- osu.Game/Screens/Edit/EditorState.cs | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorState.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 28ae7e620e..502ac7a70f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -476,6 +476,8 @@ namespace osu.Game.Screens.Edit }); resetTrack(true); + if (loader?.State != null) + restoreState(loader.State); } public override bool OnExiting(IScreen next) @@ -740,7 +742,16 @@ namespace osu.Game.Screens.Edit return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); } - protected void SwitchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState + { + Time = clock.CurrentTimeAccurate + }); + + private void restoreState([NotNull] EditorState state) + { + if (state.Time != null) + clock.Seek(state.Time.Value); + } private void cancelExit() => loader?.CancelPendingDifficultySwitch(); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 6bbfa92c3b..b6e57b0491 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -20,6 +20,13 @@ namespace osu.Game.Screens.Edit /// public class EditorLoader : ScreenWithBeatmapBackground { + /// + /// The stored state from the last editor opened. + /// This will be read by the next editor instance to be opened to restore any relevant previous state. + /// + [CanBeNull] + public EditorState State; + public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; @@ -61,7 +68,7 @@ namespace osu.Game.Screens.Edit } } - public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) + public void ScheduleDifficultySwitch(BeatmapInfo nextBeatmap, EditorState editorState) { scheduledDifficultySwitch?.Cancel(); ValidForResume = true; @@ -70,7 +77,8 @@ namespace osu.Game.Screens.Edit scheduledDifficultySwitch = Schedule(() => { - Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap); + State = editorState; // This screen is a weird exception to the rule that nothing after song select changes the global beatmap. // Because of this, we need to update the background stack's beatmap to match. diff --git a/osu.Game/Screens/Edit/EditorState.cs b/osu.Game/Screens/Edit/EditorState.cs new file mode 100644 index 0000000000..3aa92f35bc --- /dev/null +++ b/osu.Game/Screens/Edit/EditorState.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +namespace osu.Game.Screens.Edit +{ + /// + /// Structure used to transport data between instances on difficulty change. + /// It's intended to be received by from one editor instance and passed down to the next one. + /// + public class EditorState + { + /// + /// The current clock time when a difficulty switch was requested. + /// + public double? Time { get; set; } + } +} From 79d0f4835e0fbc6fd6fd28be2901c48c3ae7a1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Sep 2021 17:26:31 +0200 Subject: [PATCH 171/207] Add failing tests for preserving clipboard content --- .../Editing/TestSceneDifficultySwitching.cs | 34 +++++++++++++++++++ .../Screens/Edit/Compose/ComposeScreen.cs | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 0954bf1b49..1b548dc1ba 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -71,6 +71,40 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } + [Test] + public void TestClipboardPreservedAfterSwitch([Values] bool sameRuleset) + { + BeatmapInfo targetDifficulty = null; + + AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First())); + AddStep("copy object", () => Editor.Copy()); + + AddStep("set target difficulty", () => + { + targetDifficulty = sameRuleset + ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID) + : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID); + }); + switchToDifficulty(() => targetDifficulty); + confirmEditingBeatmap(() => targetDifficulty); + + AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any()); + AddStep("paste object", () => Editor.Paste()); + + if (sameRuleset) + AddAssert("object was pasted", () => EditorBeatmap.SelectedHitObjects.Any()); + else + AddAssert("object was not pasted", () => !EditorBeatmap.SelectedHitObjects.Any()); + + AddStep("exit editor", () => Stack.Exit()); + + AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog); + AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction()); + + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + [Test] public void TestPreventSwitchDueToUnsavedChanges() { diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 62b3d33069..e324f1edeb 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose public bool OnPressed(PlatformAction action) { if (action == PlatformAction.Copy) - host.GetClipboard().SetText(formatSelectionAsString()); + host.GetClipboard()?.SetText(formatSelectionAsString()); return false; } From 35ee889e5b1b567757632b5bb9dd0b350b5820a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Sep 2021 17:55:11 +0200 Subject: [PATCH 172/207] Restore clipboard content after difficulty switch --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- osu.Game/Screens/Edit/EditorState.cs | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 502ac7a70f..eba75f4408 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -744,13 +744,16 @@ namespace osu.Game.Screens.Edit protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState { - Time = clock.CurrentTimeAccurate + Time = clock.CurrentTimeAccurate, + ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : null }); private void restoreState([NotNull] EditorState state) { if (state.Time != null) clock.Seek(state.Time.Value); + + clipboard.Value = state.ClipboardContent ?? clipboard.Value; } private void cancelExit() => loader?.CancelPendingDifficultySwitch(); diff --git a/osu.Game/Screens/Edit/EditorState.cs b/osu.Game/Screens/Edit/EditorState.cs index 3aa92f35bc..09cc1184d2 100644 --- a/osu.Game/Screens/Edit/EditorState.cs +++ b/osu.Game/Screens/Edit/EditorState.cs @@ -15,5 +15,10 @@ namespace osu.Game.Screens.Edit /// The current clock time when a difficulty switch was requested. /// public double? Time { get; set; } + + /// + /// The current editor clipboard content at the time when a difficulty switch was requested. + /// + public string? ClipboardContent { get; set; } } } From cbb9ff1c492b51f450f8841277597ff38de61667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Sep 2021 13:44:25 +0200 Subject: [PATCH 173/207] Only run prompt-for-save test logic when relevant --- .../Visual/Editing/TestSceneDifficultySwitching.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 1b548dc1ba..c81a1abfbc 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -98,8 +98,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("exit editor", () => Stack.Exit()); - AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog); - AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction()); + if (sameRuleset) + { + AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog); + AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction()); + } // ensure editor loader didn't resume. AddAssert("stack empty", () => Stack.CurrentScreen == null); From 3df4cbca2cfd09d81226b530f96fba6a5588e2a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Sep 2021 13:45:10 +0900 Subject: [PATCH 174/207] Reduce precision of difficulty calculator tests --- osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 76f229a799..fdb3e1d465 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -23,7 +23,10 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } protected void Test(double expected, string name, params Mod[] mods) - => Assert.AreEqual(expected, CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating); + { + // Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences. + Assert.That(CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating, Is.EqualTo(expected).Within(0.00001)); + } private WorkingBeatmap getBeatmap(string name) { From eaac2bad3d31dce24d3e6a6fad03a4ff8b849e85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 13:49:02 +0900 Subject: [PATCH 175/207] Fix incorrect child margin specifications --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index bec0ea419c..2fc039ad79 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -193,13 +193,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [BackgroundDependencyLoader] private void load() { - shakeContainer = new ShakeContainer + Child = shakeContainer = new ShakeContainer { Margin = new MarginPadding(10), AutoSizeAxes = Axes.Both, Child = new FillFlowContainer { - Margin = new MarginPadding(10), Spacing = new Vector2(5), AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, @@ -218,7 +217,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge } } }; - Child = shakeContainer; joinButton.Action = () => lounge?.Join(room, passwordTextbox.Text, null, joinFailed); } From 6851e0000de64ca7bd41339eedcda4f6d3c7c99f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 13:51:18 +0900 Subject: [PATCH 176/207] Add test coverage --- .../TestSceneMultiplayerLoungeSubScreen.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 99f6ab1ae1..01bf742177 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; @@ -64,7 +65,23 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestJoinRoomWithPassword() + public void TestJoinRoomWithIncorrectPassword() + { + DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("attempt join room", () => InputManager.Key(Key.Enter)); + AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); + AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "wrong"); + AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); + + AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); + AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible); + } + + [Test] + public void TestJoinRoomWithCorrectPassword() { DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; From e3c56f9ebde94c87276af777164f61581142732a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 14:10:55 +0900 Subject: [PATCH 177/207] Show error message in popover --- .../OnlinePlay/Components/RoomManager.cs | 8 ++-- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 37 ++++++++++++++----- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 4b3617c74a..a64d89b699 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -87,10 +87,10 @@ namespace osu.Game.Screens.OnlinePlay.Components currentJoinRoomRequest.Failure += exception => { - // provide error output if the operation wasn't canceled and the error doesn't stem from an invalid password - if (!(exception is OperationCanceledException) && !((APIException)exception).Message.Equals("Invalid room password", StringComparison.Ordinal)) - Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); - onError?.Invoke(exception.ToString()); + if (exception is OperationCanceledException) + return; + + onError?.Invoke(exception.Message); }; api.Queue(currentJoinRoomRequest); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 2fc039ad79..0dec9b72bf 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -14,7 +14,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; @@ -189,9 +191,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private OsuPasswordTextBox passwordTextbox; private TriangleButton joinButton; private ShakeContainer shakeContainer; + private OsuSpriteText errorText; [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { Child = shakeContainer = new ShakeContainer { @@ -201,19 +204,32 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { Spacing = new Vector2(5), AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + Direction = FillDirection.Vertical, Children = new Drawable[] { - passwordTextbox = new OsuPasswordTextBox + new FillFlowContainer { - Width = 200, - PlaceholderText = "password", + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + passwordTextbox = new OsuPasswordTextBox + { + Width = 200, + PlaceholderText = "password", + }, + joinButton = new TriangleButton + { + Width = 80, + Text = "Join Room", + } + } }, - joinButton = new TriangleButton + errorText = new OsuSpriteText { - Width = 80, - Text = "Join Room", - } + Colour = colours.Red, + }, } } }; @@ -225,6 +241,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { passwordTextbox.Text = string.Empty; + errorText.Text = error; + errorText.FadeOutFromOne(1000, Easing.In); + shakeContainer.Shake(); } From 7bd749d0eb8bdda4997a66531691cc4f052b8481 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 14:19:55 +0900 Subject: [PATCH 178/207] Remove weird shaking --- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 0dec9b72bf..a67389e4c0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -190,47 +189,44 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private OsuPasswordTextBox passwordTextbox; private TriangleButton joinButton; - private ShakeContainer shakeContainer; private OsuSpriteText errorText; [BackgroundDependencyLoader] private void load(OsuColour colours) { - Child = shakeContainer = new ShakeContainer + Padding = new MarginPadding(10); + + Child = new FillFlowContainer { Margin = new MarginPadding(10), + Spacing = new Vector2(5), AutoSizeAxes = Axes.Both, - Child = new FillFlowContainer + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Spacing = new Vector2(5), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new FillFlowContainer { - new FillFlowContainer + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + passwordTextbox = new OsuPasswordTextBox { - passwordTextbox = new OsuPasswordTextBox - { - Width = 200, - PlaceholderText = "password", - }, - joinButton = new TriangleButton - { - Width = 80, - Text = "Join Room", - } + Width = 200, + PlaceholderText = "password", + }, + joinButton = new TriangleButton + { + Width = 80, + Text = "Join Room", } - }, - errorText = new OsuSpriteText - { - Colour = colours.Red, - }, - } + } + }, + errorText = new OsuSpriteText + { + Colour = colours.Red, + }, } }; @@ -243,8 +239,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge errorText.Text = error; errorText.FadeOutFromOne(1000, Easing.In); - - shakeContainer.Shake(); } protected override void LoadComplete() From e17b800470d18095e079699795fe475fbd0c18e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 14:44:23 +0900 Subject: [PATCH 179/207] Move shake logic into extension method --- osu.Game/Extensions/DrawableExtensions.cs | 27 +++++++++++++++ .../Graphics/Containers/ShakeContainer.cs | 34 ++----------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 52d8230fb6..b05a2994ea 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -38,6 +38,33 @@ namespace osu.Game.Extensions return repeatDelegate; } + /// + /// Shake the contents of this container. + /// + /// The target to shake. + /// The length of a single shake. + /// Pixels of displacement per shake. + /// The maximum length the shake should last. + public static void Shake(this Drawable target, double shakeDuration = 80, float shakeMagnitude = 8, double? maximumLength = null) + { + // if we don't have enough time, don't bother shaking. + if (maximumLength < shakeDuration * 2) + return; + + var sequence = target.MoveToX(shakeMagnitude, shakeDuration / 2, Easing.OutSine).Then() + .MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then(); + + // if we don't have enough time for the second shake, skip it. + if (!maximumLength.HasValue || maximumLength >= shakeDuration * 4) + { + sequence = sequence + .MoveToX(shakeMagnitude, shakeDuration, Easing.InOutSine).Then() + .MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then(); + } + + sequence.MoveToX(0, shakeDuration / 2, Easing.InSine); + } + /// /// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position. /// diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs index dca9df1e98..8a0ce287db 100644 --- a/osu.Game/Graphics/Containers/ShakeContainer.cs +++ b/osu.Game/Graphics/Containers/ShakeContainer.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Extensions; namespace osu.Game.Graphics.Containers { @@ -16,40 +16,10 @@ namespace osu.Game.Graphics.Containers /// public float ShakeDuration = 80; - /// - /// Total number of shakes. May be shortened if possible. - /// - public float TotalShakes = 4; - - /// - /// Pixels of displacement per shake. - /// - public float ShakeMagnitude = 8; - /// /// Shake the contents of this container. /// /// The maximum length the shake should last. - public void Shake(double? maximumLength = null) - { - const float shake_amount = 8; - - // if we don't have enough time, don't bother shaking. - if (maximumLength < ShakeDuration * 2) - return; - - var sequence = this.MoveToX(shake_amount, ShakeDuration / 2, Easing.OutSine).Then() - .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); - - // if we don't have enough time for the second shake, skip it. - if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4) - { - sequence = sequence - .MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then() - .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); - } - - sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine); - } + public void Shake(double? maximumLength = null) => this.Shake(ShakeDuration, maximumLength: maximumLength); } } From 8865e3cab8abdde1164f1d4cadea2a8a1dac1cb9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 14:44:32 +0900 Subject: [PATCH 180/207] Add back shake and tweak transform of text --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index a67389e4c0..11c3cfe06a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -238,7 +239,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge passwordTextbox.Text = string.Empty; errorText.Text = error; - errorText.FadeOutFromOne(1000, Easing.In); + errorText + .FadeIn() + .FlashColour(Color4.White, 200) + .Delay(1000) + .FadeOutFromOne(1000, Easing.In); + + Body.Shake(); } protected override void LoadComplete() From 5058f285048d9ac7354f02d8514203321584c44f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 14:52:50 +0900 Subject: [PATCH 181/207] Remove breaking padding --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 11c3cfe06a..310df0b91d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -195,8 +195,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [BackgroundDependencyLoader] private void load(OsuColour colours) { - Padding = new MarginPadding(10); - Child = new FillFlowContainer { Margin = new MarginPadding(10), From 6cffbee59256e0bca56d424f5b0f77605d3f195a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Sep 2021 17:22:58 +0900 Subject: [PATCH 182/207] Fix random/target mods not working in spectator --- osu.Game/Online/Spectator/SpectatorClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 2546374b21..494739797a 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -153,9 +153,9 @@ namespace osu.Game.Online.Spectator IsPlaying = true; // transfer state at point of beginning play - currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID; - currentState.RulesetID = currentRuleset.Value.ID; - currentState.Mods = currentMods.Value.Select(m => new APIMod(m)); + currentState.BeatmapID = score.ScoreInfo.Beatmap.OnlineBeatmapID; + currentState.RulesetID = score.ScoreInfo.RulesetID; + currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentBeatmap = beatmap.PlayableBeatmap; currentScore = score; From b807c161b4907633304322a9525f5569d9bc18d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Sep 2021 17:23:45 +0900 Subject: [PATCH 183/207] Remove now-unused DI params --- osu.Game/Online/Spectator/SpectatorClient.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 494739797a..8c617784b9 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -15,8 +15,6 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; @@ -46,15 +44,8 @@ namespace osu.Game.Online.Spectator private readonly BindableDictionary playingUserStates = new BindableDictionary(); private IBeatmap? currentBeatmap; - private Score? currentScore; - [Resolved] - private IBindable currentRuleset { get; set; } = null!; - - [Resolved] - private IBindable> currentMods { get; set; } = null!; - private readonly SpectatorState currentState = new SpectatorState(); /// From a7759153385c7d7d9bfa78e0b5b060e994d2d60f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Sep 2021 18:20:28 +0900 Subject: [PATCH 184/207] Fix incorrect beatmap count and SR range in multi lounge --- .../Screens/OnlinePlay/Components/ListingPollingComponent.cs | 5 +++++ .../OnlinePlay/Components/SelectionPollingComponent.cs | 3 +++ 2 files changed, 8 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index daac6a66cd..5a74df6ab1 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -57,7 +57,12 @@ namespace osu.Game.Screens.OnlinePlay.Components } foreach (var incoming in result) + { + // Copy the room to itself to populate some members (such as status and playlist expiry). + incoming.CopyFrom(incoming); + RoomManager.AddOrUpdateRoom(incoming); + } initialRoomsReceived.Value = true; tcs.SetResult(true); diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 0769d0747b..0a37d47e34 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -39,6 +39,9 @@ namespace osu.Game.Screens.OnlinePlay.Components pollReq.Success += result => { + // Copy the room to itself to populate some members (such as status and playlist expiry). + result.CopyFrom(result); + RoomManager.AddOrUpdateRoom(result); tcs.SetResult(true); }; From 2a894e7a3f560ae785e3395b55d36e8695a83741 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 23:26:02 +0900 Subject: [PATCH 185/207] Make `EditorLoader` state `private` --- osu.Game/Screens/Edit/EditorLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index b6e57b0491..5ad720f7f0 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit /// This will be read by the next editor instance to be opened to restore any relevant previous state. /// [CanBeNull] - public EditorState State; + private EditorState state; public override float BackgroundParallaxAmount => 0.1f; @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Edit scheduledDifficultySwitch = Schedule(() => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap); - State = editorState; + state = editorState; // This screen is a weird exception to the rule that nothing after song select changes the global beatmap. // Because of this, we need to update the background stack's beatmap to match. From f8bdca542d2a6d993b3b0d4a6ddc7ae87ddaf9a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 23:36:17 +0900 Subject: [PATCH 186/207] Make restoring state a `public` call on `Editor` --- osu.Game/Screens/Edit/Editor.cs | 22 +++++++++++----------- osu.Game/Screens/Edit/EditorLoader.cs | 8 +++++++- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index eba75f4408..5bb47e1c11 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -317,6 +317,16 @@ namespace osu.Game.Screens.Edit /// public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); + /// + /// Restore the editor to a provided state. + /// + /// The state to restore. + public void RestoreState([NotNull] EditorState state) => Schedule(() => + { + clock.Seek(state.Time); + clipboard.Value = state.ClipboardContent; + }); + protected void Save() { // no longer new after first user-triggered save. @@ -476,8 +486,6 @@ namespace osu.Game.Screens.Edit }); resetTrack(true); - if (loader?.State != null) - restoreState(loader.State); } public override bool OnExiting(IScreen next) @@ -745,17 +753,9 @@ namespace osu.Game.Screens.Edit protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : null + ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : string.Empty }); - private void restoreState([NotNull] EditorState state) - { - if (state.Time != null) - clock.Seek(state.Time.Value); - - clipboard.Value = state.ClipboardContent ?? clipboard.Value; - } - private void cancelExit() => loader?.CancelPendingDifficultySwitch(); public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 5ad720f7f0..2a01a5b6b2 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -91,7 +91,13 @@ namespace osu.Game.Screens.Edit private void pushEditor() { - this.Push(CreateEditor()); + var editor = CreateEditor(); + + this.Push(editor); + + if (state != null) + editor.RestoreState(state); + ValidForResume = false; } From 57f8ccca167d48e79c3c5e89b8d9db6d4a7febe9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 23:36:26 +0900 Subject: [PATCH 187/207] Remove nullability from `EditorState` properties Also update the xmldoc to not be specific to difficulty switching --- osu.Game/Screens/Edit/EditorState.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorState.cs b/osu.Game/Screens/Edit/EditorState.cs index 09cc1184d2..4690074e3d 100644 --- a/osu.Game/Screens/Edit/EditorState.cs +++ b/osu.Game/Screens/Edit/EditorState.cs @@ -6,19 +6,18 @@ namespace osu.Game.Screens.Edit { /// - /// Structure used to transport data between instances on difficulty change. - /// It's intended to be received by from one editor instance and passed down to the next one. + /// Structure used to convey the general state of an instance. /// public class EditorState { /// - /// The current clock time when a difficulty switch was requested. + /// The current audio time. /// - public double? Time { get; set; } + public double Time { get; set; } /// - /// The current editor clipboard content at the time when a difficulty switch was requested. + /// The editor clipboard content. /// - public string? ClipboardContent { get; set; } + public string ClipboardContent { get; set; } = string.Empty; } } From baf99619343ff49ed240c8cc09e3db9453dfda2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 Sep 2021 22:50:45 +0200 Subject: [PATCH 188/207] Amend xmldoc of shake extension method --- osu.Game/Extensions/DrawableExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index b05a2994ea..03cc345947 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -39,7 +39,7 @@ namespace osu.Game.Extensions } /// - /// Shake the contents of this container. + /// Shakes this drawable. /// /// The target to shake. /// The length of a single shake. From 4b3ab42ffd8cb2b9c60490315dce8470637dffcd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Sep 2021 13:18:46 +0900 Subject: [PATCH 189/207] Ensure beatmap is populated --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 8 +++++++- .../Visual/Gameplay/TestSceneReplayRecording.cs | 8 +++++++- .../Visual/Gameplay/TestSceneSpectatorPlayback.cs | 4 +++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index b38f7a998d..aded934b88 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -17,10 +17,12 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -54,7 +56,11 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay }) + Recorder = recorder = new TestReplayRecorder(new Score + { + Replay = replay, + ScoreInfo = { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo } + }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 6e338b7202..37fd1a77d5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -13,10 +13,12 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -45,7 +47,11 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder(new Score { Replay = replay }) + Recorder = new TestReplayRecorder(new Score + { + Replay = replay, + ScoreInfo = { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo } + }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) }, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index bb577886cc..039daf39e2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -25,11 +25,13 @@ using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -354,7 +356,7 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { public TestReplayRecorder() - : base(new Score()) + : base(new Score { ScoreInfo = { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo } }) { } From a2dcef7c0aa1acd3557007e3c410443862318512 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 13:37:30 +0900 Subject: [PATCH 190/207] Use local (or barebones `BeatmapInfo`) where feasible --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 4 +--- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs | 4 +--- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index aded934b88..2ce0213ea2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -17,12 +17,10 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -59,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay, - ScoreInfo = { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo } + ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo } }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 37fd1a77d5..85a2870bf9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -13,12 +13,10 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay Recorder = new TestReplayRecorder(new Score { Replay = replay, - ScoreInfo = { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo } + ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo } }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 039daf39e2..d9d0dc6c58 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -25,13 +25,11 @@ using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -356,7 +354,7 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { public TestReplayRecorder() - : base(new Score { ScoreInfo = { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo } }) + : base(new Score { ScoreInfo = { Beatmap = new BeatmapInfo() } }) { } From f9af24df23e88659793eb629ab13be689dfb3d5f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Sep 2021 15:22:27 +0900 Subject: [PATCH 191/207] Fix mania hitobject tests --- .../Skinning/ManiaHitObjectTestScene.cs | 4 +-- .../Skinning/ManiaSkinnableTestScene.cs | 25 ++----------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs index b7d7af6b8c..68cf3b67df 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { c.Add(CreateHitObject().With(h => { - h.HitObject.StartTime = START_TIME; + h.HitObject.StartTime = Time.Current + 5000; h.AccentColour.Value = Color4.Orange; })); }) @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { c.Add(CreateHitObject().With(h => { - h.HitObject.StartTime = START_TIME; + h.HitObject.StartTime = Time.Current + 5000; h.AccentColour.Value = Color4.Orange; })); }) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 1d84a2dfcb..ddfd057cd8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning /// public abstract class ManiaSkinnableTestScene : SkinnableTestScene { - protected const double START_TIME = 1000000000; - [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); @@ -55,27 +53,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning public readonly Bindable Direction = new Bindable(); IBindable IScrollingInfo.Direction => Direction; - IBindable IScrollingInfo.TimeRange { get; } = new Bindable(1000); - IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm(); - } - - private class ZeroScrollAlgorithm : IScrollAlgorithm - { - public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) - => double.MinValue; - - public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) - => scrollLength; - - public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) - => (float)((time - START_TIME) / timeRange) * scrollLength; - - public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) - => 0; - - public void Reset() - { - } + IBindable IScrollingInfo.TimeRange { get; } = new Bindable(5000); + IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm(); } } } From 187c557ea8105dc1db329cdf6828bc2a79fc41dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Aug 2021 02:21:45 +0900 Subject: [PATCH 192/207] Begin migrating settings implementation across to realm --- osu.Game/Configuration/RealmSetting.cs | 33 +++++++++++++++++++++++++ osu.Game/Configuration/SettingsStore.cs | 25 ++++++++++--------- osu.Game/OsuGameBase.cs | 4 +-- osu.Game/Rulesets/RulesetConfigCache.cs | 4 +-- 4 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Configuration/RealmSetting.cs diff --git a/osu.Game/Configuration/RealmSetting.cs b/osu.Game/Configuration/RealmSetting.cs new file mode 100644 index 0000000000..b773796067 --- /dev/null +++ b/osu.Game/Configuration/RealmSetting.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Database; +using Realms; + +namespace osu.Game.Configuration +{ + [MapTo(@"Setting")] + public class RealmSetting : RealmObject, IHasGuidPrimaryKey + { + [PrimaryKey] + public Guid ID { get; set; } = Guid.NewGuid(); + + public int? RulesetID { get; set; } + + public int? Variant { get; set; } + + public string Key { get; set; } + + [MapTo(nameof(Value))] + public string ValueString { get; set; } + + public object Value + { + get => ValueString; + set => ValueString = value.ToString(); + } + + public override string ToString() => $"{Key}=>{Value}"; + } +} diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index 86e84b0732..864fa3cf53 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -1,31 +1,34 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Game.Database; +using Realms; namespace osu.Game.Configuration { - public class SettingsStore : DatabaseBackedStore + public class RealmSettingsStore { - public event Action SettingChanged; + private readonly RealmContextFactory realmFactory; - public SettingsStore(DatabaseContextFactory contextFactory) - : base(contextFactory) + public RealmSettingsStore(RealmContextFactory realmFactory) { + this.realmFactory = realmFactory; } /// - /// Retrieve s for a specified ruleset/variant content. + /// Retrieve s for a specified ruleset/variant content. /// /// The ruleset's internal ID. /// An optional variant. - public List Query(int? rulesetId = null, int? variant = null) => - ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + public List Query(int? rulesetId = null, int? variant = null) + { + using (var context = realmFactory.GetForRead()) + return context.Realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + } - public void Update(DatabasedSetting setting) + public void Update(RealmSetting setting) { using (ContextFactory.GetForWrite()) { @@ -33,11 +36,9 @@ namespace osu.Game.Configuration Refresh(ref setting); setting.Value = newValue; } - - SettingChanged?.Invoke(); } - public void Delete(DatabasedSetting setting) + public void Delete(RealmSetting setting) { using (var usage = ContextFactory.GetForWrite()) usage.Context.Remove(setting); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f4db0f2603..12f53df6e8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -140,7 +140,7 @@ namespace osu.Game private FileStore fileStore; - private SettingsStore settingsStore; + private RealmSettingsStore settingsStore; private RulesetConfigCache rulesetConfigCache; @@ -279,7 +279,7 @@ namespace osu.Game migrateDataToRealm(); - dependencies.Cache(settingsStore = new SettingsStore(contextFactory)); + dependencies.Cache(settingsStore = new RealmSettingsStore(realmFactory)); dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(settingsStore)); var powerStatus = CreateBatteryInfo(); diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index d42428638c..885fa249df 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -16,9 +16,9 @@ namespace osu.Game.Rulesets public class RulesetConfigCache : Component { private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); - private readonly SettingsStore settingsStore; + private readonly RealmSettingsStore settingsStore; - public RulesetConfigCache(SettingsStore settingsStore) + public RulesetConfigCache(RealmSettingsStore settingsStore) { this.settingsStore = settingsStore; } From 14314476f07ab0271ac49fbfa8a557d838676b04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Sep 2021 20:57:19 +0900 Subject: [PATCH 193/207] Update realm to latest version --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 941656bb70..5a302c5349 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From a2f1752344061e1f9ae4f7a4679f872de90feeaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 14:39:47 +0900 Subject: [PATCH 194/207] Make settings works with current caching structure Will likely pull out that `RulesetConfigCache` next, but this is an "everything works" state. --- .../ManiaRulesetConfigManager.cs | 6 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 +- .../Configuration/OsuRulesetConfigManager.cs | 6 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 40 ++++++------- .../Testing/TestSceneRulesetDependencies.cs | 4 +- .../Configuration/DatabasedConfigManager.cs | 59 ++++++------------- osu.Game/Configuration/SettingsStore.cs | 21 +------ osu.Game/OsuGameBase.cs | 5 +- .../Settings/RulesetSettingsSubsection.cs | 4 +- .../Configuration/RulesetConfigManager.cs | 5 +- osu.Game/Rulesets/Ruleset.cs | 34 +++++------ osu.Game/Rulesets/RulesetConfigCache.cs | 34 ++++++++--- 12 files changed, 101 insertions(+), 121 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index ac8168dfc9..d9bd0ab609 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -3,7 +3,7 @@ using System; using osu.Framework.Configuration.Tracking; -using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Mania.UI; @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Mania.Configuration { public class ManiaRulesetConfigManager : RulesetConfigManager { - public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) - : base(settings, ruleset, variant) + public ManiaRulesetConfigManager(RealmContextFactory realmFactory, RulesetInfo ruleset, int? variant = null) + : base(realmFactory, ruleset, variant) { } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 1f79dae280..7ad27b94eb 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -17,7 +17,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; -using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Mania public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); + public override IRulesetConfigManager CreateConfig(RealmContextFactory realmFactory) => new ManiaRulesetConfigManager(realmFactory, RulesetInfo); public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this); diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index 9589fd576f..23c25c6558 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.UI; @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Configuration { public class OsuRulesetConfigManager : RulesetConfigManager { - public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) - : base(settings, ruleset, variant) + public OsuRulesetConfigManager(RealmContextFactory realmFactory, RulesetInfo ruleset, int? variant = null) + : base(realmFactory, ruleset, variant) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index f4a93a571d..b7cb0c5313 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -1,41 +1,41 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; +using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Replays.Types; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; -using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Skinning; -using System; -using System.Linq; -using osu.Framework.Extensions.EnumExtensions; +using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Setup; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu { @@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Osu public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + public override IRulesetConfigManager CreateConfig(RealmContextFactory realmFactory) => new OsuRulesetConfigManager(realmFactory, RulesetInfo); protected override IEnumerable GetValidHitResults() { diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 8c6932e792..12960fd4d0 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Testing } public override IResourceStore CreateResourceStore() => new NamespacedResourceStore(TestResources.GetStore(), @"Resources"); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager(); + public override IRulesetConfigManager CreateConfig(RealmContextFactory realmFactory) => new TestRulesetConfigManager(); public override IEnumerable GetModsFor(ModType type) => Array.Empty(); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null; diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index b3783b45a8..0514d11e24 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Game.Database; using osu.Game.Rulesets; namespace osu.Game.Configuration @@ -13,19 +14,17 @@ namespace osu.Game.Configuration public abstract class DatabasedConfigManager : ConfigManager where TLookup : struct, Enum { - private readonly SettingsStore settings; + private readonly RealmContextFactory realmFactory; private readonly int? variant; - private List databasedSettings; + private List databasedSettings; private readonly RulesetInfo ruleset; - private bool legacySettingsExist; - - protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null) + protected DatabasedConfigManager(RealmContextFactory realmFactory, RulesetInfo ruleset = null, int? variant = null) { - this.settings = settings; + this.realmFactory = realmFactory; this.ruleset = ruleset; this.variant = variant; @@ -36,39 +35,22 @@ namespace osu.Game.Configuration protected override void PerformLoad() { - databasedSettings = settings.Query(ruleset?.ID, variant); - legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out _)); + var rulesetID = ruleset?.ID; + + // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. + databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetID && b.Variant == variant).ToList(); } protected override bool PerformSave() { - lock (dirtySettings) - { - foreach (var setting in dirtySettings) - settings.Update(setting); - dirtySettings.Clear(); - } - + // do nothing, realm saves immediately return true; } - private readonly List dirtySettings = new List(); - protected override void AddBindable(TLookup lookup, Bindable bindable) { base.AddBindable(lookup, bindable); - if (legacySettingsExist) - { - var legacySetting = databasedSettings.Find(s => s.Key == ((int)(object)lookup).ToString()); - - if (legacySetting != null) - { - bindable.Parse(legacySetting.Value); - settings.Delete(legacySetting); - } - } - var setting = databasedSettings.Find(s => s.Key == lookup.ToString()); if (setting != null) @@ -77,12 +59,15 @@ namespace osu.Game.Configuration } else { - settings.Update(setting = new DatabasedSetting + realmFactory.Context.Write(() => { - Key = lookup.ToString(), - Value = bindable.Value, - RulesetID = ruleset?.ID, - Variant = variant, + realmFactory.Context.Add(setting = new RealmSetting + { + Key = lookup.ToString(), + Value = bindable.Value, + RulesetID = ruleset?.ID, + Variant = variant, + }); }); databasedSettings.Add(setting); @@ -90,13 +75,7 @@ namespace osu.Game.Configuration bindable.ValueChanged += b => { - setting.Value = b.NewValue; - - lock (dirtySettings) - { - if (!dirtySettings.Contains(setting)) - dirtySettings.Add(setting); - } + realmFactory.Context.Write(() => setting.Value = b.NewValue); }; } } diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index 864fa3cf53..49775927b1 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -4,15 +4,14 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Database; -using Realms; namespace osu.Game.Configuration { - public class RealmSettingsStore + public class SettingsStore { private readonly RealmContextFactory realmFactory; - public RealmSettingsStore(RealmContextFactory realmFactory) + public SettingsStore(RealmContextFactory realmFactory) { this.realmFactory = realmFactory; } @@ -27,21 +26,5 @@ namespace osu.Game.Configuration using (var context = realmFactory.GetForRead()) return context.Realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); } - - public void Update(RealmSetting setting) - { - using (ContextFactory.GetForWrite()) - { - var newValue = setting.Value; - Refresh(ref setting); - setting.Value = newValue; - } - } - - public void Delete(RealmSetting setting) - { - using (var usage = ContextFactory.GetForWrite()) - usage.Context.Remove(setting); - } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 12f53df6e8..600465b823 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -140,8 +140,6 @@ namespace osu.Game private FileStore fileStore; - private RealmSettingsStore settingsStore; - private RulesetConfigCache rulesetConfigCache; private SpectatorClient spectatorClient; @@ -279,8 +277,7 @@ namespace osu.Game migrateDataToRealm(); - dependencies.Cache(settingsStore = new RealmSettingsStore(realmFactory)); - dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(settingsStore)); + dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(realmFactory, RulesetStore)); var powerStatus = CreateBatteryInfo(); if (powerStatus != null) diff --git a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs index 93b07fbac7..9ff5b8935a 100644 --- a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings { /// /// A which provides subclasses with the - /// from the 's . + /// from the 's . /// public abstract class RulesetSettingsSubsection : SettingsSubsection { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 0ff3455f00..17dbd30103 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -3,14 +3,15 @@ using System; using osu.Game.Configuration; +using osu.Game.Database; namespace osu.Game.Rulesets.Configuration { public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager where TLookup : struct, Enum { - protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) - : base(settings, ruleset, variant) + protected RulesetConfigManager(RealmContextFactory realmFactory, RulesetInfo ruleset, int? variant = null) + : base(realmFactory, ruleset, variant) { } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index b0c3836774..cf4ea4f01d 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -5,32 +5,32 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; +using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.IO.Stores; +using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.UI; using osu.Game.Beatmaps.Legacy; -using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Extensions; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Skinning; -using osu.Game.Users; -using JetBrains.Annotations; -using osu.Framework.Extensions; -using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Testing; -using osu.Game.Extensions; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Filter; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; +using osu.Game.Users; namespace osu.Game.Rulesets { @@ -262,8 +262,8 @@ namespace osu.Game.Rulesets /// /// Creates the for this . /// - /// The to store the settings. - public virtual IRulesetConfigManager CreateConfig(SettingsStore settings) => null; + /// The to store the settings. + public virtual IRulesetConfigManager CreateConfig(RealmContextFactory realmFactory) => null; /// /// A unique short name to reference this ruleset in online requests. diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 885fa249df..afdf3219df 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets.Configuration; namespace osu.Game.Rulesets @@ -15,12 +15,29 @@ namespace osu.Game.Rulesets /// public class RulesetConfigCache : Component { - private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); - private readonly RealmSettingsStore settingsStore; + private readonly RealmContextFactory realmFactory; + private readonly RulesetStore rulesets; - public RulesetConfigCache(RealmSettingsStore settingsStore) + private readonly Dictionary configCache = new Dictionary(); + + public RulesetConfigCache(RealmContextFactory realmFactory, RulesetStore rulesets) { - this.settingsStore = settingsStore; + this.realmFactory = realmFactory; + this.rulesets = rulesets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // let's keep things simple for now and just retrieve all the required configs at startup.. + foreach (var ruleset in rulesets.AvailableRulesets) + { + if (ruleset.ID == null) + continue; + + configCache[ruleset.ID.Value] = ruleset.CreateInstance().CreateConfig(realmFactory); + } } /// @@ -34,7 +51,10 @@ namespace osu.Game.Rulesets if (ruleset.RulesetInfo.ID == null) return null; - return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore)); + if (!configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var config)) + return null; + + return config; } protected override void Dispose(bool isDisposing) From ac377a2e3c39235fa18b4be85b90b744b174a2cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 14:56:40 +0900 Subject: [PATCH 195/207] Remove unused `SettingsStore` --- .../Visual/Navigation/TestSceneOsuGame.cs | 1 - osu.Game/Configuration/SettingsStore.cs | 30 ------------------- 2 files changed, 31 deletions(-) delete mode 100644 osu.Game/Configuration/SettingsStore.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index b8232837b5..43459408d5 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -75,7 +75,6 @@ namespace osu.Game.Tests.Visual.Navigation typeof(FileStore), typeof(ScoreManager), typeof(BeatmapManager), - typeof(SettingsStore), typeof(RulesetConfigCache), typeof(OsuColour), typeof(IBindable), diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs deleted file mode 100644 index 49775927b1..0000000000 --- a/osu.Game/Configuration/SettingsStore.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using System.Linq; -using osu.Game.Database; - -namespace osu.Game.Configuration -{ - public class SettingsStore - { - private readonly RealmContextFactory realmFactory; - - public SettingsStore(RealmContextFactory realmFactory) - { - this.realmFactory = realmFactory; - } - - /// - /// Retrieve s for a specified ruleset/variant content. - /// - /// The ruleset's internal ID. - /// An optional variant. - public List Query(int? rulesetId = null, int? variant = null) - { - using (var context = realmFactory.GetForRead()) - return context.Realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); - } - } -} From 2bcb3fd304ad8ca70bc1b998239ee10f70b94b1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 15:22:16 +0900 Subject: [PATCH 196/207] Add migration of existing settings --- osu.Game/OsuGameBase.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 600465b823..489d5b5e51 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -469,6 +469,25 @@ namespace osu.Game db.Context.RemoveRange(existingBindings); + var existingSettings = db.Context.DatabasedSetting; + + // only migrate data if the realm database is empty. + if (!usage.Realm.All().Any()) + { + foreach (var dkb in existingSettings) + { + usage.Realm.Add(new RealmSetting + { + ValueString = dkb.StringValue, + Key = dkb.Key, + RulesetID = dkb.RulesetID, + Variant = dkb.Variant + }); + } + } + + db.Context.RemoveRange(existingSettings); + usage.Commit(); } } From f54d554d3099ecf845573bbcb9bda1c4250edc3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Sep 2021 17:03:26 +0900 Subject: [PATCH 197/207] Extract removal to method --- osu.Game/Online/Rooms/Room.cs | 15 ++++++++++----- .../Components/ListingPollingComponent.cs | 4 +--- .../Components/SelectionPollingComponent.cs | 4 +--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d964060f10..5f71b4be4a 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -179,11 +179,7 @@ namespace osu.Game.Online.Rooms if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); - // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, - // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. - // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. - if (!(Status.Value is RoomStatusEnded)) - other.Playlist.RemoveAll(i => i.Expired); + other.RemoveExpiredPlaylistItems(); if (!Playlist.SequenceEqual(other.Playlist)) { @@ -200,6 +196,15 @@ namespace osu.Game.Online.Rooms Position.Value = other.Position.Value; } + public void RemoveExpiredPlaylistItems() + { + // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, + // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. + // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. + if (!(Status.Value is RoomStatusEnded)) + Playlist.RemoveAll(i => i.Expired); + } + public bool ShouldSerializeRoomID() => false; public bool ShouldSerializeHost() => false; public bool ShouldSerializeEndDate() => false; diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index 5a74df6ab1..fcf7767958 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -58,9 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Components foreach (var incoming in result) { - // Copy the room to itself to populate some members (such as status and playlist expiry). - incoming.CopyFrom(incoming); - + incoming.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(incoming); } diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 0a37d47e34..b9d2bdf23e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -39,9 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Components pollReq.Success += result => { - // Copy the room to itself to populate some members (such as status and playlist expiry). - result.CopyFrom(result); - + result.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(result); tcs.SetResult(true); }; From 5bb741b4e8126327e9fd4ccd76c76142f8fab39a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 15:22:43 +0900 Subject: [PATCH 198/207] Remove migration of key bindings --- osu.Game/Database/OsuDbContext.cs | 9 +---- .../Input/Bindings/DatabasedKeyBinding.cs | 39 ------------------- osu.Game/OsuGameBase.cs | 20 +--------- 3 files changed, 2 insertions(+), 66 deletions(-) delete mode 100644 osu.Game/Input/Bindings/DatabasedKeyBinding.cs diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 68d186c65d..1d8322aadd 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -12,7 +12,6 @@ using osu.Game.Configuration; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Scoring; -using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; using LogLevel = Microsoft.Extensions.Logging.LogLevel; using osu.Game.Skinning; @@ -24,14 +23,13 @@ namespace osu.Game.Database public DbSet BeatmapDifficulty { get; set; } public DbSet BeatmapMetadata { get; set; } public DbSet BeatmapSetInfo { get; set; } - public DbSet DatabasedSetting { get; set; } public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } public DbSet SkinInfo { get; set; } public DbSet ScoreInfo { get; set; } // migrated to realm - public DbSet DatabasedKeyBinding { get; set; } + public DbSet DatabasedSetting { get; set; } private readonly string connectionString; @@ -138,11 +136,6 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity().HasIndex(b => b.DeletePending); - modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); - modelBuilder.Entity().HasIndex(b => b.IntAction); - modelBuilder.Entity().Ignore(b => b.KeyCombination); - modelBuilder.Entity().Ignore(b => b.Action); - modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs deleted file mode 100644 index ad3493d0fc..0000000000 --- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.ComponentModel.DataAnnotations.Schema; -using osu.Framework.Input.Bindings; -using osu.Game.Database; - -namespace osu.Game.Input.Bindings -{ - [Table("KeyBinding")] - public class DatabasedKeyBinding : IKeyBinding, IHasPrimaryKey - { - public int ID { get; set; } - - public int? RulesetID { get; set; } - - public int? Variant { get; set; } - - [Column("Keys")] - public string KeysString { get; set; } - - [Column("Action")] - public int IntAction { get; set; } - - [NotMapped] - public KeyCombination KeyCombination - { - get => KeysString; - set => KeysString = value.ToString(); - } - - [NotMapped] - public object Action - { - get => IntAction; - set => IntAction = (int)value; - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 489d5b5e51..ee97b27265 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -450,25 +450,7 @@ namespace osu.Game using (var db = contextFactory.GetForWrite()) using (var usage = realmFactory.GetForWrite()) { - var existingBindings = db.Context.DatabasedKeyBinding; - - // only migrate data if the realm database is empty. - if (!usage.Realm.All().Any()) - { - foreach (var dkb in existingBindings) - { - usage.Realm.Add(new RealmKeyBinding - { - KeyCombinationString = dkb.KeyCombination.ToString(), - ActionInt = (int)dkb.Action, - RulesetID = dkb.RulesetID, - Variant = dkb.Variant - }); - } - } - - db.Context.RemoveRange(existingBindings); - + // migrate ruleset settings. can be removed 20220315. var existingSettings = db.Context.DatabasedSetting; // only migrate data if the realm database is empty. From c36a67d06e718d7e5d05730c7110cb141d2e8ecb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 15:01:48 +0900 Subject: [PATCH 199/207] Fix some tests failing due to using a locally constructed ruleset --- osu.Game/Rulesets/RulesetConfigCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index afdf3219df..f2c3121320 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets return null; if (!configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var config)) - return null; + return ruleset.CreateConfig(realmFactory); return config; } From 520e5507645fe640073c25c9fc9e62e768a8ea80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 16:08:31 +0900 Subject: [PATCH 200/207] Bring back `SettingsStore` to avoid changing ruleset API for now Also fixes some remaining test failures due to locally constructed rulesets that are not being tracked by the game. --- .../ManiaRulesetConfigManager.cs | 6 +-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 +- .../Configuration/OsuRulesetConfigManager.cs | 6 +-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 44 +++++++++---------- .../Testing/TestSceneRulesetDependencies.cs | 4 +- .../Configuration/DatabasedConfigManager.cs | 32 +++++++------- osu.Game/Configuration/SettingsStore.cs | 20 +++++++++ .../Settings/RulesetSettingsSubsection.cs | 4 +- .../Configuration/RulesetConfigManager.cs | 5 +-- osu.Game/Rulesets/Ruleset.cs | 30 ++++++------- osu.Game/Rulesets/RulesetConfigCache.cs | 9 +++- 11 files changed, 95 insertions(+), 69 deletions(-) create mode 100644 osu.Game/Configuration/SettingsStore.cs diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index d9bd0ab609..ac8168dfc9 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -3,7 +3,7 @@ using System; using osu.Framework.Configuration.Tracking; -using osu.Game.Database; +using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Mania.UI; @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Mania.Configuration { public class ManiaRulesetConfigManager : RulesetConfigManager { - public ManiaRulesetConfigManager(RealmContextFactory realmFactory, RulesetInfo ruleset, int? variant = null) - : base(realmFactory, ruleset, variant) + public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + : base(settings, ruleset, variant) { } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 7ad27b94eb..1f79dae280 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -17,7 +17,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; -using osu.Game.Database; +using osu.Game.Configuration; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Mania public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); - public override IRulesetConfigManager CreateConfig(RealmContextFactory realmFactory) => new ManiaRulesetConfigManager(realmFactory, RulesetInfo); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this); diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index 23c25c6558..9589fd576f 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Database; +using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.UI; @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Configuration { public class OsuRulesetConfigManager : RulesetConfigManager { - public OsuRulesetConfigManager(RealmContextFactory realmFactory, RulesetInfo ruleset, int? variant = null) - : base(realmFactory, ruleset, variant) + public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + : base(settings, ruleset, variant) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b7cb0c5313..f4a93a571d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -1,41 +1,41 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Database; -using osu.Game.Graphics; using osu.Game.Overlays.Settings; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; -using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Rulesets.Osu.Edit.Setup; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Skinning; +using System; +using System.Linq; +using osu.Framework.Extensions.EnumExtensions; +using osu.Game.Rulesets.Osu.Edit.Setup; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Statistics; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; -using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu { @@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Osu public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); - public override IRulesetConfigManager CreateConfig(RealmContextFactory realmFactory) => new OsuRulesetConfigManager(realmFactory, RulesetInfo); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); protected override IEnumerable GetValidHitResults() { diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 12960fd4d0..8c6932e792 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Testing } public override IResourceStore CreateResourceStore() => new NamespacedResourceStore(TestResources.GetStore(), @"Resources"); - public override IRulesetConfigManager CreateConfig(RealmContextFactory realmFactory) => new TestRulesetConfigManager(); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager(); public override IEnumerable GetModsFor(ModType type) => Array.Empty(); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null; diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 0514d11e24..d6988e31b5 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -18,13 +18,13 @@ namespace osu.Game.Configuration private readonly int? variant; - private List databasedSettings; + private List databasedSettings = new List(); private readonly RulesetInfo ruleset; - protected DatabasedConfigManager(RealmContextFactory realmFactory, RulesetInfo ruleset = null, int? variant = null) + protected DatabasedConfigManager(SettingsStore store = null, RulesetInfo ruleset = null, int? variant = null) { - this.realmFactory = realmFactory; + realmFactory = store?.Realm; this.ruleset = ruleset; this.variant = variant; @@ -37,8 +37,11 @@ namespace osu.Game.Configuration { var rulesetID = ruleset?.ID; - // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. - databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetID && b.Variant == variant).ToList(); + if (realmFactory != null) + { + // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. + databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetID && b.Variant == variant).ToList(); + } } protected override bool PerformSave() @@ -59,23 +62,22 @@ namespace osu.Game.Configuration } else { - realmFactory.Context.Write(() => + setting = new RealmSetting { - realmFactory.Context.Add(setting = new RealmSetting - { - Key = lookup.ToString(), - Value = bindable.Value, - RulesetID = ruleset?.ID, - Variant = variant, - }); - }); + Key = lookup.ToString(), + Value = bindable.Value, + RulesetID = ruleset?.ID, + Variant = variant, + }; + + realmFactory?.Context.Write(() => realmFactory.Context.Add(setting)); databasedSettings.Add(setting); } bindable.ValueChanged += b => { - realmFactory.Context.Write(() => setting.Value = b.NewValue); + realmFactory?.Context.Write(() => setting.Value = b.NewValue); }; } } diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs new file mode 100644 index 0000000000..2bba20fb09 --- /dev/null +++ b/osu.Game/Configuration/SettingsStore.cs @@ -0,0 +1,20 @@ +// 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.Database; + +namespace osu.Game.Configuration +{ + public class SettingsStore + { + // this class mostly exists as a wrapper to avoid breaking the ruleset API (see usage in RulesetConfigManager). + // it may cease to exist going forward, depending on how the structure of the config data layer changes. + + public readonly RealmContextFactory Realm; + + public SettingsStore(RealmContextFactory realmFactory) + { + Realm = realmFactory; + } + } +} diff --git a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs index 9ff5b8935a..93b07fbac7 100644 --- a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Database; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings { /// /// A which provides subclasses with the - /// from the 's . + /// from the 's . /// public abstract class RulesetSettingsSubsection : SettingsSubsection { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 17dbd30103..0ff3455f00 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -3,15 +3,14 @@ using System; using osu.Game.Configuration; -using osu.Game.Database; namespace osu.Game.Rulesets.Configuration { public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager where TLookup : struct, Enum { - protected RulesetConfigManager(RealmContextFactory realmFactory, RulesetInfo ruleset, int? variant = null) - : base(realmFactory, ruleset, variant) + protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + : base(settings, ruleset, variant) { } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index cf4ea4f01d..b0c3836774 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -5,32 +5,32 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; -using osu.Framework.Extensions; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.IO.Stores; -using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Database; -using osu.Game.Extensions; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; using osu.Game.Users; +using JetBrains.Annotations; +using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Testing; +using osu.Game.Extensions; +using osu.Game.Rulesets.Filter; +using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets { @@ -262,8 +262,8 @@ namespace osu.Game.Rulesets /// /// Creates the for this . /// - /// The to store the settings. - public virtual IRulesetConfigManager CreateConfig(RealmContextFactory realmFactory) => null; + /// The to store the settings. + public virtual IRulesetConfigManager CreateConfig(SettingsStore settings) => null; /// /// A unique short name to reference this ruleset in online requests. diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index f2c3121320..aeac052673 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets.Configuration; @@ -30,13 +31,15 @@ namespace osu.Game.Rulesets { base.LoadComplete(); + var settingsStore = new SettingsStore(realmFactory); + // let's keep things simple for now and just retrieve all the required configs at startup.. foreach (var ruleset in rulesets.AvailableRulesets) { if (ruleset.ID == null) continue; - configCache[ruleset.ID.Value] = ruleset.CreateInstance().CreateConfig(realmFactory); + configCache[ruleset.ID.Value] = ruleset.CreateInstance().CreateConfig(settingsStore); } } @@ -52,7 +55,9 @@ namespace osu.Game.Rulesets return null; if (!configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var config)) - return ruleset.CreateConfig(realmFactory); + // any ruleset request which wasn't initialised on startup should not be stored to realm. + // this should only be used by tests. + return ruleset.CreateConfig(null); return config; } From 80ecf81be34ce87b350612b3932913e2556cbce8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 16:55:42 +0900 Subject: [PATCH 201/207] Rename all databased setting classes to be specific to rulesets for now --- .../Configuration/DatabasedConfigManager.cs | 84 ------------------- ...RealmSetting.cs => RealmRulesetSetting.cs} | 4 +- osu.Game/OsuGameBase.cs | 4 +- .../Configuration/RulesetConfigManager.cs | 73 +++++++++++++++- 4 files changed, 74 insertions(+), 91 deletions(-) delete mode 100644 osu.Game/Configuration/DatabasedConfigManager.cs rename osu.Game/Configuration/{RealmSetting.cs => RealmRulesetSetting.cs} (87%) diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs deleted file mode 100644 index d6988e31b5..0000000000 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Configuration; -using osu.Game.Database; -using osu.Game.Rulesets; - -namespace osu.Game.Configuration -{ - public abstract class DatabasedConfigManager : ConfigManager - where TLookup : struct, Enum - { - private readonly RealmContextFactory realmFactory; - - private readonly int? variant; - - private List databasedSettings = new List(); - - private readonly RulesetInfo ruleset; - - protected DatabasedConfigManager(SettingsStore store = null, RulesetInfo ruleset = null, int? variant = null) - { - realmFactory = store?.Realm; - this.ruleset = ruleset; - this.variant = variant; - - Load(); - - InitialiseDefaults(); - } - - protected override void PerformLoad() - { - var rulesetID = ruleset?.ID; - - if (realmFactory != null) - { - // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. - databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetID && b.Variant == variant).ToList(); - } - } - - protected override bool PerformSave() - { - // do nothing, realm saves immediately - return true; - } - - protected override void AddBindable(TLookup lookup, Bindable bindable) - { - base.AddBindable(lookup, bindable); - - var setting = databasedSettings.Find(s => s.Key == lookup.ToString()); - - if (setting != null) - { - bindable.Parse(setting.Value); - } - else - { - setting = new RealmSetting - { - Key = lookup.ToString(), - Value = bindable.Value, - RulesetID = ruleset?.ID, - Variant = variant, - }; - - realmFactory?.Context.Write(() => realmFactory.Context.Add(setting)); - - databasedSettings.Add(setting); - } - - bindable.ValueChanged += b => - { - realmFactory?.Context.Write(() => setting.Value = b.NewValue); - }; - } - } -} diff --git a/osu.Game/Configuration/RealmSetting.cs b/osu.Game/Configuration/RealmRulesetSetting.cs similarity index 87% rename from osu.Game/Configuration/RealmSetting.cs rename to osu.Game/Configuration/RealmRulesetSetting.cs index b773796067..7623d1f948 100644 --- a/osu.Game/Configuration/RealmSetting.cs +++ b/osu.Game/Configuration/RealmRulesetSetting.cs @@ -7,8 +7,8 @@ using Realms; namespace osu.Game.Configuration { - [MapTo(@"Setting")] - public class RealmSetting : RealmObject, IHasGuidPrimaryKey + [MapTo(@"RulesetSetting")] + public class RealmRulesetSetting : RealmObject, IHasGuidPrimaryKey { [PrimaryKey] public Guid ID { get; set; } = Guid.NewGuid(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ee97b27265..4e4061db9d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -454,11 +454,11 @@ namespace osu.Game var existingSettings = db.Context.DatabasedSetting; // only migrate data if the realm database is empty. - if (!usage.Realm.All().Any()) + if (!usage.Realm.All().Any()) { foreach (var dkb in existingSettings) { - usage.Realm.Add(new RealmSetting + usage.Realm.Add(new RealmRulesetSetting { ValueString = dkb.StringValue, Key = dkb.Key, diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 0ff3455f00..3f5472e52c 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -2,16 +2,83 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Configuration; using osu.Game.Configuration; +using osu.Game.Database; namespace osu.Game.Rulesets.Configuration { - public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager + public abstract class RulesetConfigManager : ConfigManager, IRulesetConfigManager where TLookup : struct, Enum { - protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) - : base(settings, ruleset, variant) + private readonly RealmContextFactory realmFactory; + + private readonly int? variant; + + private List databasedSettings = new List(); + + private readonly RulesetInfo ruleset; + + protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int? variant = null) { + realmFactory = store?.Realm; + this.ruleset = ruleset; + this.variant = variant; + + Load(); + + InitialiseDefaults(); + } + + protected override void PerformLoad() + { + var rulesetID = ruleset?.ID; + + if (realmFactory != null) + { + // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. + databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetID && b.Variant == variant).ToList(); + } + } + + protected override bool PerformSave() + { + // do nothing, realm saves immediately + return true; + } + + protected override void AddBindable(TLookup lookup, Bindable bindable) + { + base.AddBindable(lookup, bindable); + + var setting = databasedSettings.Find(s => s.Key == lookup.ToString()); + + if (setting != null) + { + bindable.Parse(setting.Value); + } + else + { + setting = new RealmRulesetSetting + { + Key = lookup.ToString(), + Value = bindable.Value, + RulesetID = ruleset?.ID, + Variant = variant, + }; + + realmFactory?.Context.Write(() => realmFactory.Context.Add(setting)); + + databasedSettings.Add(setting); + } + + bindable.ValueChanged += b => + { + realmFactory?.Context.Write(() => setting.Value = b.NewValue); + }; } } } From dcfe9c67e3019f91e5b92f1e433dae48d4c79843 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 17:01:31 +0900 Subject: [PATCH 202/207] Make ruleset id non-nullable --- osu.Game/Configuration/RealmRulesetSetting.cs | 3 ++- osu.Game/OsuGameBase.cs | 4 +++- .../Configuration/RulesetConfigManager.cs | 15 +++++++++------ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/RealmRulesetSetting.cs b/osu.Game/Configuration/RealmRulesetSetting.cs index 7623d1f948..c88a261ad2 100644 --- a/osu.Game/Configuration/RealmRulesetSetting.cs +++ b/osu.Game/Configuration/RealmRulesetSetting.cs @@ -13,7 +13,8 @@ namespace osu.Game.Configuration [PrimaryKey] public Guid ID { get; set; } = Guid.NewGuid(); - public int? RulesetID { get; set; } + [Indexed] + public int RulesetID { get; set; } public int? Variant { get; set; } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4e4061db9d..36406ded08 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -458,11 +458,13 @@ namespace osu.Game { foreach (var dkb in existingSettings) { + if (dkb.RulesetID == null) continue; + usage.Realm.Add(new RealmRulesetSetting { ValueString = dkb.StringValue, Key = dkb.Key, - RulesetID = dkb.RulesetID, + RulesetID = dkb.RulesetID.Value, Variant = dkb.Variant }); } diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 3f5472e52c..a97976392a 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -20,12 +20,17 @@ namespace osu.Game.Rulesets.Configuration private List databasedSettings = new List(); - private readonly RulesetInfo ruleset; + private readonly int rulesetId; protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int? variant = null) { realmFactory = store?.Realm; - this.ruleset = ruleset; + + if (realmFactory != null && !ruleset.ID.HasValue) + throw new InvalidOperationException("Attempted to add databased settings for a non-databased ruleset"); + + rulesetId = ruleset.ID ?? -1; + this.variant = variant; Load(); @@ -35,12 +40,10 @@ namespace osu.Game.Rulesets.Configuration protected override void PerformLoad() { - var rulesetID = ruleset?.ID; - if (realmFactory != null) { // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. - databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetID && b.Variant == variant).ToList(); + databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); } } @@ -66,7 +69,7 @@ namespace osu.Game.Rulesets.Configuration { Key = lookup.ToString(), Value = bindable.Value, - RulesetID = ruleset?.ID, + RulesetID = rulesetId, Variant = variant, }; From 15e3f95c87dfef94deccc3f0d349c1b8c32bfb3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 17:02:39 +0900 Subject: [PATCH 203/207] Remove remnants of `DatabasedSetting` from `SkinInfo` This was never used --- osu.Game/Skinning/SkinInfo.cs | 3 --- osu.Game/Skinning/SkinStore.cs | 6 ------ 2 files changed, 9 deletions(-) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 851d71f914..2bf8668ec6 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using osu.Framework.Extensions.ObjectExtensions; -using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; @@ -39,8 +38,6 @@ namespace osu.Game.Skinning public List Files { get; set; } = new List(); - public List Settings { get; set; } - public bool DeletePending { get; set; } public static SkinInfo Default { get; } = new SkinInfo diff --git a/osu.Game/Skinning/SkinStore.cs b/osu.Game/Skinning/SkinStore.cs index 153eeda130..31cadb0a24 100644 --- a/osu.Game/Skinning/SkinStore.cs +++ b/osu.Game/Skinning/SkinStore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; -using Microsoft.EntityFrameworkCore; using osu.Framework.Platform; using osu.Game.Database; @@ -14,9 +12,5 @@ namespace osu.Game.Skinning : base(contextFactory, storage) { } - - protected override IQueryable AddIncludesForDeletion(IQueryable query) => - base.AddIncludesForDeletion(query) - .Include(s => s.Settings); // don't include FileInfo. these are handled by the FileStore itself. } } From a150fb29960bde3dcd9150dc4143eaea4cbead5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 17:09:02 +0900 Subject: [PATCH 204/207] Add nullability directive and make variant non-nullable --- osu.Game/Configuration/RealmRulesetSetting.cs | 9 ++++++--- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Rulesets/Configuration/RulesetConfigManager.cs | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/RealmRulesetSetting.cs b/osu.Game/Configuration/RealmRulesetSetting.cs index c88a261ad2..d1e1bb9ea2 100644 --- a/osu.Game/Configuration/RealmRulesetSetting.cs +++ b/osu.Game/Configuration/RealmRulesetSetting.cs @@ -5,6 +5,8 @@ using System; using osu.Game.Database; using Realms; +#nullable enable + namespace osu.Game.Configuration { [MapTo(@"RulesetSetting")] @@ -16,12 +18,13 @@ namespace osu.Game.Configuration [Indexed] public int RulesetID { get; set; } - public int? Variant { get; set; } + [Indexed] + public int Variant { get; set; } - public string Key { get; set; } + public string Key { get; set; } = string.Empty; [MapTo(nameof(Value))] - public string ValueString { get; set; } + public string ValueString { get; set; } = string.Empty; public object Value { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 36406ded08..46614ca0ad 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -465,7 +465,7 @@ namespace osu.Game ValueString = dkb.StringValue, Key = dkb.Key, RulesetID = dkb.RulesetID.Value, - Variant = dkb.Variant + Variant = dkb.Variant ?? 0, }); } } diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index a97976392a..f4b4e2978c 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Configuration { private readonly RealmContextFactory realmFactory; - private readonly int? variant; + private readonly int variant; private List databasedSettings = new List(); @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Configuration rulesetId = ruleset.ID ?? -1; - this.variant = variant; + this.variant = variant ?? 0; Load(); From a1d325cb22fb066f82f8561d46c2757214b38573 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 17:12:00 +0900 Subject: [PATCH 205/207] Mark key and value non-nullable (at realm end) and simplify `Value` logic --- osu.Game/Configuration/RealmRulesetSetting.cs | 13 ++++--------- osu.Game/OsuGameBase.cs | 2 +- .../Rulesets/Configuration/RulesetConfigManager.cs | 4 ++-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/osu.Game/Configuration/RealmRulesetSetting.cs b/osu.Game/Configuration/RealmRulesetSetting.cs index d1e1bb9ea2..07e56ad8dd 100644 --- a/osu.Game/Configuration/RealmRulesetSetting.cs +++ b/osu.Game/Configuration/RealmRulesetSetting.cs @@ -21,17 +21,12 @@ namespace osu.Game.Configuration [Indexed] public int Variant { get; set; } + [Required] public string Key { get; set; } = string.Empty; - [MapTo(nameof(Value))] - public string ValueString { get; set; } = string.Empty; + [Required] + public string Value { get; set; } = string.Empty; - public object Value - { - get => ValueString; - set => ValueString = value.ToString(); - } - - public override string ToString() => $"{Key}=>{Value}"; + public override string ToString() => $"{Key} => {Value}"; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 46614ca0ad..59a05aec4f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -462,8 +462,8 @@ namespace osu.Game usage.Realm.Add(new RealmRulesetSetting { - ValueString = dkb.StringValue, Key = dkb.Key, + Value = dkb.StringValue, RulesetID = dkb.RulesetID.Value, Variant = dkb.Variant ?? 0, }); diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index f4b4e2978c..a0ec8e3e0e 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Configuration setting = new RealmRulesetSetting { Key = lookup.ToString(), - Value = bindable.Value, + Value = bindable.Value.ToString(), RulesetID = rulesetId, Variant = variant, }; @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Configuration bindable.ValueChanged += b => { - realmFactory?.Context.Write(() => setting.Value = b.NewValue); + realmFactory?.Context.Write(() => setting.Value = b.NewValue.ToString()); }; } } From cdb44d7239e3ad92a56e40bd04ce5a6ee7ad86c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Sep 2021 17:16:08 +0900 Subject: [PATCH 206/207] Fix match footer test scene not working in visual testing --- .../Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 4e08ffef17..44a8d7b439 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -15,11 +16,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room(); - Child = new MultiplayerMatchFooter + Child = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Height = 50 + RelativeSizeAxes = Axes.X, + Height = 50, + Child = new MultiplayerMatchFooter() }; }); } From 4f1db5af40c79e038737eef75ab6859641b12fa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Sep 2021 17:25:07 +0900 Subject: [PATCH 207/207] Attach migration memo to `DatabasedSetting` class for visibility --- osu.Game/Configuration/DatabasedSetting.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index f5c92b3029..fe1d51d57f 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -7,7 +7,7 @@ using osu.Game.Database; namespace osu.Game.Configuration { [Table("Settings")] - public class DatabasedSetting : IHasPrimaryKey + public class DatabasedSetting : IHasPrimaryKey // can be removed 20220315. { public int ID { get; set; }