From c124034cf3f671cc44b950851bb6a2f3900524cc Mon Sep 17 00:00:00 2001 From: dekrain Date: Wed, 16 Feb 2022 23:18:14 +0100 Subject: [PATCH 01/37] Add text displaying recent score time --- .../Online/Leaderboards/LeaderboardScore.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index c2393a5de5..f77982535d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -29,6 +30,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Online.Leaderboards { @@ -42,6 +44,7 @@ namespace osu.Game.Online.Leaderboards private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 35; + private const float date_width = 35; protected Container RankContainer { get; private set; } @@ -100,10 +103,18 @@ namespace osu.Game.Online.Leaderboards RelativeSizeAxes = Axes.Y, Width = rank_width, }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = date_width, + Child = new DateLabel(Score.Date), + }, content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_width, }, + Padding = new MarginPadding { Left = rank_width, Right = date_width, }, Children = new Drawable[] { new Container @@ -377,6 +388,36 @@ namespace osu.Game.Online.Leaderboards public LocalisableString TooltipText { get; } } + private class DateLabel : DrawableDate + { + public DateLabel(DateTimeOffset date) + : base(date, 20) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + protected override string Format() + { + var now = DateTime.Now; + var difference = now - Date; + + // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference.TotalMinutes < 1) + return CommonStrings.TimeNow.ToString(); + if (difference.TotalHours < 1) + return $@"{Math.Ceiling(difference.TotalMinutes)}min"; + if (difference.TotalDays < 1) + return $@"{Math.Ceiling(difference.TotalHours)}h"; + if (difference.TotalDays < 7) + return $@"{Math.Ceiling(difference.TotalDays)}d"; + + return string.Empty; + } + } + public class LeaderboardScoreStatistic { public IconUsage Icon; From 333a305af3dcabba5591b17140be49ba4c5c14f3 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 00:09:17 +0100 Subject: [PATCH 02/37] Use floor instead of ceiling --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index f77982535d..9ac1ab8075 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -408,11 +408,11 @@ namespace osu.Game.Online.Leaderboards if (difference.TotalMinutes < 1) return CommonStrings.TimeNow.ToString(); if (difference.TotalHours < 1) - return $@"{Math.Ceiling(difference.TotalMinutes)}min"; + return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) - return $@"{Math.Ceiling(difference.TotalHours)}h"; + return $@"{Math.Floor(difference.TotalHours)}h"; if (difference.TotalDays < 7) - return $@"{Math.Ceiling(difference.TotalDays)}d"; + return $@"{Math.Floor(difference.TotalDays)}d"; return string.Empty; } From cb9ffc655acf4509ce174c770788b9bfeb715339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Feb 2022 22:32:42 +0100 Subject: [PATCH 03/37] Add tests showing expected behaviour of naming helper --- osu.Game.Tests/Utils/NamingUtilsTest.cs | 132 ++++++++++++++++++++++++ osu.Game/Utils/NamingUtils.cs | 15 +++ 2 files changed, 147 insertions(+) create mode 100644 osu.Game.Tests/Utils/NamingUtilsTest.cs create mode 100644 osu.Game/Utils/NamingUtils.cs diff --git a/osu.Game.Tests/Utils/NamingUtilsTest.cs b/osu.Game.Tests/Utils/NamingUtilsTest.cs new file mode 100644 index 0000000000..62e688db90 --- /dev/null +++ b/osu.Game.Tests/Utils/NamingUtilsTest.cs @@ -0,0 +1,132 @@ +// 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.Utils; + +namespace osu.Game.Tests.Utils +{ + [TestFixture] + public class NamingUtilsTest + { + [Test] + public void TestEmptySet() + { + string nextBestName = NamingUtils.GetNextBestName(Enumerable.Empty(), "New Difficulty"); + + Assert.AreEqual("New Difficulty", nextBestName); + } + + [Test] + public void TestNotTaken() + { + string[] existingNames = + { + "Something", + "Entirely", + "Different" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty", nextBestName); + } + + [Test] + public void TestNotTakenButClose() + { + string[] existingNames = + { + "New Difficulty(1)", + "New Difficulty (abcd)", + "New Difficulty but not really" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty", nextBestName); + } + + [Test] + public void TestAlreadyTaken() + { + string[] existingNames = + { + "New Difficulty" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (1)", nextBestName); + } + + [Test] + public void TestAlreadyTakenWithDifferentCase() + { + string[] existingNames = + { + "new difficulty" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (1)", nextBestName); + } + + [Test] + public void TestAlreadyTakenWithBrackets() + { + string[] existingNames = + { + "new difficulty (copy)" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty (copy)"); + + Assert.AreEqual("New Difficulty (copy) (1)", nextBestName); + } + + [Test] + public void TestMultipleAlreadyTaken() + { + string[] existingNames = + { + "New Difficulty", + "New difficulty (1)", + "new Difficulty (2)", + "New DIFFICULTY (3)" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (4)", nextBestName); + } + + [Test] + public void TestEvenMoreAlreadyTaken() + { + string[] existingNames = Enumerable.Range(1, 30).Select(i => $"New Difficulty ({i})").Append("New Difficulty").ToArray(); + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (31)", nextBestName); + } + + [Test] + public void TestMultipleAlreadyTakenWithGaps() + { + string[] existingNames = + { + "New Difficulty", + "New Difficulty (1)", + "New Difficulty (4)", + "New Difficulty (9)" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (2)", nextBestName); + } + } +} diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs new file mode 100644 index 0000000000..1b48f57932 --- /dev/null +++ b/osu.Game/Utils/NamingUtils.cs @@ -0,0 +1,15 @@ +// 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; + +namespace osu.Game.Utils +{ + public static class NamingUtils + { + public static string GetNextBestName(IEnumerable existingNames, string desiredName) + { + return null; + } + } +} From e09570c31bf18af340d529fc59448b1b694db270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Feb 2022 23:00:45 +0100 Subject: [PATCH 04/37] Implement best-name-finding helper method --- osu.Game/Utils/NamingUtils.cs | 48 ++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index 1b48f57932..482e3d0954 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -2,14 +2,60 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Text.RegularExpressions; namespace osu.Game.Utils { public static class NamingUtils { + /// + /// Given a set of and a target , + /// finds a "best" name closest to that is not in . + /// + /// + /// + /// This helper is most useful in scenarios when creating new objects in a set + /// (such as adding new difficulties to a beatmap set, or creating a clone of an existing object that needs a unique name). + /// If is already present in , + /// this method will append the lowest possible number in brackets that doesn't conflict with + /// to and return that. + /// See osu.Game.Tests.Utils.NamingUtilsTest for concrete examples of behaviour. + /// + /// + /// and are compared in a case-insensitive manner, + /// so this method is safe to use for naming files in a platform-invariant manner. + /// + /// public static string GetNextBestName(IEnumerable existingNames, string desiredName) { - return null; + string pattern = $@"^(?i){Regex.Escape(desiredName)}(?-i)( \((?[1-9][0-9]*)\))?$"; + var regex = new Regex(pattern, RegexOptions.Compiled); + var takenNumbers = new HashSet(); + + foreach (string name in existingNames) + { + var match = regex.Match(name); + if (!match.Success) + continue; + + string copyNumberString = match.Groups[@"copyNumber"].Value; + + if (string.IsNullOrEmpty(copyNumberString)) + { + takenNumbers.Add(0); + continue; + } + + takenNumbers.Add(int.Parse(copyNumberString)); + } + + int bestNumber = 0; + while (takenNumbers.Contains(bestNumber)) + bestNumber += 1; + + return bestNumber == 0 + ? desiredName + : $"{desiredName} ({bestNumber})"; } } } From 8a08bb7aaf88ec33c64c01659cd0ea9c42f93f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Feb 2022 23:12:13 +0100 Subject: [PATCH 05/37] Use best-name-finding helper in new difficulty creation flow --- osu.Game/Beatmaps/BeatmapManager.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 777d5db2ad..5f7de0d762 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -22,6 +22,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Stores; +using osu.Game.Utils; #nullable enable @@ -123,7 +124,10 @@ namespace osu.Game.Beatmaps { var playableBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(rulesetInfo); - var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone()); + var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone()) + { + DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty") + }; var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; foreach (var timingPoint in playableBeatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); @@ -150,8 +154,10 @@ namespace osu.Game.Beatmaps newBeatmap.BeatmapInfo = newBeatmapInfo = referenceWorkingBeatmap.BeatmapInfo.Clone(); // assign a new ID to the clone. newBeatmapInfo.ID = Guid.NewGuid(); - // add "(copy)" suffix to difficulty name to avoid clashes on save. - newBeatmapInfo.DifficultyName += " (copy)"; + // add "(copy)" suffix to difficulty name, and additionally ensure that it doesn't conflict with any other potentially pre-existing copies. + newBeatmapInfo.DifficultyName = NamingUtils.GetNextBestName( + targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), + $"{newBeatmapInfo.DifficultyName} (copy)"); // clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. newBeatmapInfo.Hash = string.Empty; // clear online properties. From e459523afe5cd351e98a8558f41ca933ef3374b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Feb 2022 23:13:43 +0100 Subject: [PATCH 06/37] Adjust beatmap creation test cases to new behaviour --- .../Editing/TestSceneEditorBeatmapCreation.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 0a2f622da1..ecd4035edd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -269,11 +269,12 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestCreateNewBeatmapFailsWithBlankNamedDifficulties() + public void TestCreateMultipleNewDifficultiesSucceeds() { Guid setId = Guid.Empty; AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); + AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "New Difficulty"); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => { @@ -282,15 +283,24 @@ namespace osu.Game.Tests.Visual.Editing }); AddStep("try to create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddAssert("beatmap set unchanged", () => + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction()); + + AddUntilStep("wait for created", () => + { + string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != "New Difficulty"; + }); + AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)"); + AddAssert("new difficulty persisted", () => { var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); - return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2); }); } [Test] - public void TestCreateNewBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset) + public void TestSavingBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset) { Guid setId = Guid.Empty; const string duplicate_difficulty_name = "duplicate"; From 39a7bbdb9ad0cd23ccffc45b0760b228a078393d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Feb 2022 14:03:38 +0900 Subject: [PATCH 07/37] Fix mania PP calculator applying incorrect score multiplier --- .../Difficulty/ManiaPerformanceCalculator.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 8a8c41bb8a..722cb55036 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -43,14 +43,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease); - - double scoreMultiplier = 1.0; - foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m))) - scoreMultiplier *= m.ScoreMultiplier; - - // Scale score up, so it's comparable to other keymods - scaledScore *= 1.0 / scoreMultiplier; + if (Attributes.ScoreMultiplier > 0) + { + // Scale score up, so it's comparable to other keymods + scaledScore *= 1.0 / Attributes.ScoreMultiplier; + } // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. From 7bd731ae08210e58d2101aa3afef7807e15e4fa0 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 10:12:35 +0100 Subject: [PATCH 08/37] Move the date next to the flag icon --- .../Online/Leaderboards/LeaderboardScore.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9ac1ab8075..a13d8eeffd 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -44,7 +44,6 @@ namespace osu.Game.Online.Leaderboards private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 35; - private const float date_width = 35; protected Container RankContainer { get; private set; } @@ -59,7 +58,7 @@ namespace osu.Game.Online.Leaderboards public GlowingSpriteText ScoreText { get; private set; } - private Container flagBadgeContainer; + private FillFlowContainer flagBadgeAndDateContainer; private FillFlowContainer modsContainer; private List statisticsLabels; @@ -103,18 +102,10 @@ namespace osu.Game.Online.Leaderboards RelativeSizeAxes = Axes.Y, Width = rank_width, }, - new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = date_width, - Child = new DateLabel(Score.Date), - }, content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_width, Right = date_width, }, + Padding = new MarginPadding { Left = rank_width }, Children = new Drawable[] { new Container @@ -176,10 +167,12 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(10f, 0f), Children = new Drawable[] { - flagBadgeContainer = new Container + flagBadgeAndDateContainer = new FillFlowContainer { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5f, 0f), Size = new Vector2(87f, 20f), Masking = true, Children = new Drawable[] @@ -189,6 +182,10 @@ namespace osu.Game.Online.Leaderboards Width = 30, RelativeSizeAxes = Axes.Y, }, + new DateLabel(Score.Date) + { + RelativeSizeAxes = Axes.Y, + }, }, }, new FillFlowContainer @@ -254,7 +251,7 @@ namespace osu.Game.Online.Leaderboards public override void Show() { - foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeContainer, modsContainer }.Concat(statisticsLabels)) + foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels)) d.FadeOut(); Alpha = 0; @@ -281,7 +278,7 @@ namespace osu.Game.Online.Leaderboards using (BeginDelayedSequence(50)) { - var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray(); + var drawables = new Drawable[] { flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels).ToArray(); for (int i = 0; i < drawables.Length; i++) drawables[i].FadeIn(100 + i * 50); } @@ -391,10 +388,9 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { public DateLabel(DateTimeOffset date) - : base(date, 20) + : base(date) { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } protected override string Format() @@ -411,7 +407,7 @@ namespace osu.Game.Online.Leaderboards return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) return $@"{Math.Floor(difference.TotalHours)}h"; - if (difference.TotalDays < 7) + if (difference.TotalDays < 3) return $@"{Math.Floor(difference.TotalDays)}d"; return string.Empty; From f4d1e6f600343fda0ca9446297e9f8d0e5073ce6 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 10:38:29 +0100 Subject: [PATCH 09/37] Add tests for timerefs --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++++++ osu.Game/Online/Leaderboards/LeaderboardScore.cs | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 667fd08084..2f44633f4d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -197,6 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now, Mods = new Mod[] { new OsuModHidden(), @@ -234,6 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-30), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -254,6 +256,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-70), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -275,6 +278,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddMinutes(-40), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -296,6 +300,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-2), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -317,6 +322,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9826, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-25), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -338,6 +344,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9654, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-50), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -359,6 +366,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-72), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a13d8eeffd..8cfba68ea6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -401,8 +401,10 @@ namespace osu.Game.Online.Leaderboards // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) // requires pluralisable string support framework-side - if (difference.TotalMinutes < 1) + if (difference.TotalSeconds < 10) return CommonStrings.TimeNow.ToString(); + if (difference.TotalMinutes < 1) + return $@"{Math.Floor(difference.TotalSeconds)}s"; if (difference.TotalHours < 1) return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) From 3d5ed24e206a43990f36880464917045e8cdf2be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 21:04:59 +0900 Subject: [PATCH 10/37] Fix beatmap overlay leaderboards and links not working Completely aware that this isn't how it should be done, but would like to get this out in a hotfix release today. Maybe changes opinions on https://github.com/ppy/osu/pull/16890 structure? --- .../API/Requests/Responses/APIBeatmap.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index dca60e54cb..c53fab48ae 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -112,7 +112,27 @@ namespace osu.Game.Online.API.Requests.Responses public int OnlineID { get; set; } = -1; public string Name => $@"{nameof(APIRuleset)} (ID: {OnlineID})"; - public string ShortName => nameof(APIRuleset); + + public string ShortName + { + get + { + // TODO: this should really not exist. + switch (OnlineID) + { + case 0: return "osu"; + + case 1: return "taiko"; + + case 2: return "catch"; + + case 3: return "fruits"; + + default: throw new ArgumentOutOfRangeException(); + } + } + } + public string InstantiationInfo => string.Empty; public Ruleset CreateInstance() => throw new NotImplementedException(); From 9d0023c750a85c2cc78a6d6d5004f50231916a92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 21:12:51 +0900 Subject: [PATCH 11/37] Fix incorrect mappings --- osu.Game/Online/API/Requests/Responses/APIBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index c53fab48ae..f5795141c5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -124,9 +124,9 @@ namespace osu.Game.Online.API.Requests.Responses case 1: return "taiko"; - case 2: return "catch"; + case 2: return "fruits"; - case 3: return "fruits"; + case 3: return "mania"; default: throw new ArgumentOutOfRangeException(); } From 08317b4265c94a6f6b30a6629d002175a7945309 Mon Sep 17 00:00:00 2001 From: OctopuSSX Date: Thu, 17 Feb 2022 20:43:36 +0300 Subject: [PATCH 12/37] Update ScreenshotManager.cs --- osu.Game/Graphics/ScreenshotManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index a39d7bfb47..b0f20de685 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -112,6 +112,8 @@ namespace osu.Game.Graphics if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false) cursorVisibility.Value = true; + host.GetClipboard()?.SetImage(image); + string filename = getFilename(); if (filename == null) return; From 1abbb9ab39675f4caeba0b9700aa9fb4fd9ca0be Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 21:26:59 +0100 Subject: [PATCH 13/37] Align the bar to be on baseline of score components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Online/Leaderboards/LeaderboardScore.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 8cfba68ea6..4f7670f098 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -160,8 +160,8 @@ namespace osu.Game.Online.Leaderboards }, new FillFlowContainer { - Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), @@ -169,29 +169,32 @@ namespace osu.Game.Online.Leaderboards { flagBadgeAndDateContainer = new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(5f, 0f), - Size = new Vector2(87f, 20f), + Width = 87f, Masking = true, Children = new Drawable[] { new UpdateableFlag(user.Country) { - Width = 30, - RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(30f, 20f), }, new DateLabel(Score.Date) { - RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, }, }, new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Margin = new MarginPadding { Left = edge_margin }, From e49da2948d15d4d175d3de7ca7e47656e85e41b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:24:18 +0900 Subject: [PATCH 14/37] Fix storyboard background replacement logic not working for beatmaps with multiple backgrounds In the case where the background image of individual difficulties is different, querying the beatmap *set*'s metadata as we were will cause issues. I haven't added test coverage for this but can if required. Can be manually tested using https://osu.ppy.sh/beatmapsets/1595773#osu/3377474 (specifically the highest difficulty). Closes https://github.com/ppy/osu/discussions/16873. --- osu.Game/Storyboards/Storyboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index b86deeab89..c4864c0334 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -78,7 +78,7 @@ namespace osu.Game.Storyboards { get { - string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata.BackgroundFile; + string backgroundPath = BeatmapInfo.Metadata.BackgroundFile; if (string.IsNullOrEmpty(backgroundPath)) return false; From 15ed9ec4fa725f08c6e49ca0192542f2b7aee6ac Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 20:47:02 +0100 Subject: [PATCH 15/37] Merge scoreboard and leaderboard implementations together --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 + .../Online/Leaderboards/LeaderboardScore.cs | 23 +------ .../BeatmapSet/Scores/ScoreboardTime.cs | 40 +---------- osu.Game/Utils/ScoreboardTimeUtils.cs | 66 +++++++++++++++++++ 4 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 osu.Game/Utils/ScoreboardTimeUtils.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2f44633f4d..c4178143f8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -388,6 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddMonths(-3), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -409,6 +410,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddYears(-2), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 4f7670f098..faec0abce6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -30,7 +30,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; -using osu.Game.Resources.Localisation.Web; +using osu.Framework.Utils; namespace osu.Game.Online.Leaderboards { @@ -398,24 +398,7 @@ namespace osu.Game.Online.Leaderboards protected override string Format() { - var now = DateTime.Now; - var difference = now - Date; - - // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - if (difference.TotalSeconds < 10) - return CommonStrings.TimeNow.ToString(); - if (difference.TotalMinutes < 1) - return $@"{Math.Floor(difference.TotalSeconds)}s"; - if (difference.TotalHours < 1) - return $@"{Math.Floor(difference.TotalMinutes)}min"; - if (difference.TotalDays < 1) - return $@"{Math.Floor(difference.TotalHours)}h"; - if (difference.TotalDays < 3) - return $@"{Math.Floor(difference.TotalDays)}d"; - - return string.Empty; + return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index ff1d3490b4..ceb446f310 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,9 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; using osu.Game.Graphics; -using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -16,41 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - { - var now = DateTime.Now; - var difference = now - Date; - - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - if (difference.TotalHours < 1) - return CommonStrings.TimeNow.ToString(); - if (difference.TotalDays < 1) - return "hr".ToQuantity((int)difference.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (Date > now.AddMonths(-1)) - return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; - - for (int months = 1; months <= 11; ++months) - { - if (Date > now.AddMonths(-(months + 1))) - return months == 1 ? "1mo" : $"{months}mos"; - } - - int years = 1; - while (Date <= now.AddYears(-(years + 1))) - years += 1; - return years == 1 ? "1yr" : $"{years}yrs"; - } + => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs new file mode 100644 index 0000000000..7e84b8f2d0 --- /dev/null +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Resources.Localisation.Web; + +#nullable enable + +namespace osu.Game.Utils +{ + public static class ScoreboardTimeUtils + { + public static string FormatQuantity(string template, int quantity) + { + if (quantity <= 1) + return $@"{quantity}{template}"; + return $@"{quantity}{template}s"; + } + + public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) + { + // This function fails if the passed in time is something close to an epoch + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + var now = DateTime.Now; + var span = now - time; + + if (span < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (span.TotalMinutes < 1) + return FormatQuantity("sec", (int)span.TotalSeconds); + if (span.TotalHours < 1) + return FormatQuantity("min", (int)span.TotalMinutes); + if (span.TotalDays < 1) + return FormatQuantity("hr", (int)span.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return FormatQuantity("dy", (int)span.TotalDays); + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return FormatQuantity("mo", months); + } + + int years = 1; + // DateTime causes a crash here for epoch + while (time <= now.AddYears(-(years + 1))) + years += 1; + return FormatQuantity("yr", years); + } + } +} From 0d83c5a39a02581c3eb5d702c143ec55c904f461 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 20:47:30 +0100 Subject: [PATCH 16/37] Add colour highlighting recent scores --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index faec0abce6..217be5a024 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -390,6 +390,9 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { + public static readonly Colour4 COLOUR_SATURATED = Colour4.Lime; + public static readonly Colour4 COLOUR_UNSATURATED = Colour4.White; + public DateLabel(DateTimeOffset date) : base(date) { @@ -398,6 +401,15 @@ namespace osu.Game.Online.Leaderboards protected override string Format() { + var now = DateTime.Now; + var difference = now - Date; + + const double seconds_to_blank = 60*45; + const double tense_factor = 2.325; + + double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); + Colour = Interpolation.ValueAt(tf, COLOUR_SATURATED, COLOUR_UNSATURATED, 0, 1); + return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } } From 31b7ce053d24cef175f3b4b399d1a9e79b86fc32 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 21:18:26 +0100 Subject: [PATCH 17/37] Fix CI issues --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 217be5a024..aaf889d06c 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -404,7 +404,7 @@ namespace osu.Game.Online.Leaderboards var now = DateTime.Now; var difference = now - Date; - const double seconds_to_blank = 60*45; + const double seconds_to_blank = 60 * 45; const double tense_factor = 2.325; double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 7e84b8f2d0..86639c1034 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -14,13 +14,12 @@ namespace osu.Game.Utils { if (quantity <= 1) return $@"{quantity}{template}"; + return $@"{quantity}{template}s"; } public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) { - // This function fails if the passed in time is something close to an epoch - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. // this is intended to be a best-effort, more legible approximation of that. // compare: @@ -57,10 +56,13 @@ namespace osu.Game.Utils } int years = 1; - // DateTime causes a crash here for epoch - while (time <= now.AddYears(-(years + 1))) + // Add upper bound to prevent a crash + while (years < 20 && time <= now.AddYears(-(years + 1))) years += 1; - return FormatQuantity("yr", years); + if (years < 20) + return FormatQuantity("yr", years); + + return "never"; } } } From 262751a98a22d87ef5406203d5b38f94e65d30e0 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 21:23:35 +0100 Subject: [PATCH 18/37] Revert highlighting recent scores --- .../Online/Leaderboards/LeaderboardScore.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index aaf889d06c..a8b508829d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -390,28 +390,13 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { - public static readonly Colour4 COLOUR_SATURATED = Colour4.Lime; - public static readonly Colour4 COLOUR_UNSATURATED = Colour4.White; - public DateLabel(DateTimeOffset date) : base(date) { Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() - { - var now = DateTime.Now; - var difference = now - Date; - - const double seconds_to_blank = 60 * 45; - const double tense_factor = 2.325; - - double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); - Colour = Interpolation.ValueAt(tf, COLOUR_SATURATED, COLOUR_UNSATURATED, 0, 1); - - return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); - } + protected override string Format() => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic From e20ae5b87100a387045471902547cd2296841519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 17 Feb 2022 23:54:29 +0100 Subject: [PATCH 19/37] Add all colour constants for "basic" colour theme to `OsuColour` --- osu.Game/Graphics/OsuColour.cs | 57 +++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index f63bd53549..567d665e81 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -264,32 +264,53 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); - /// - /// Equivalent to 's . - /// - public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378"); + #region "Basic" colour theme - /// - /// Equivalent to 's . - /// + // Reference: https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Asset%2FColours?node-id=1838%3A3 + + public readonly Color4 Pink0 = Color4Extensions.FromHex(@"ff99c7"); + public readonly Color4 Pink1 = Color4Extensions.FromHex(@"ff66ab"); + public readonly Color4 Pink2 = Color4Extensions.FromHex(@"eb4791"); + public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378"); + public readonly Color4 Pink4 = Color4Extensions.FromHex(@"6b2e49"); + + public readonly Color4 Purple0 = Color4Extensions.FromHex(@"b299ff"); + public readonly Color4 Purple1 = Color4Extensions.FromHex(@"8c66ff"); + public readonly Color4 Purple2 = Color4Extensions.FromHex(@"7047eb"); + public readonly Color4 Purple3 = Color4Extensions.FromHex(@"5933cc"); + public readonly Color4 Purple4 = Color4Extensions.FromHex(@"3d2e6b"); + + public readonly Color4 Blue0 = Color4Extensions.FromHex(@"99ddff"); + public readonly Color4 Blue1 = Color4Extensions.FromHex(@"66ccff"); + public readonly Color4 Blue2 = Color4Extensions.FromHex(@"47b4eb"); public readonly Color4 Blue3 = Color4Extensions.FromHex(@"3399cc"); + public readonly Color4 Blue4 = Color4Extensions.FromHex(@"2e576b"); + + public readonly Color4 Green0 = Color4Extensions.FromHex(@"99ffa2"); + public readonly Color4 Green1 = Color4Extensions.FromHex(@"66ff73"); + public readonly Color4 Green2 = Color4Extensions.FromHex(@"47eb55"); + public readonly Color4 Green3 = Color4Extensions.FromHex(@"33cc40"); + public readonly Color4 Green4 = Color4Extensions.FromHex(@"2e6b33"); public readonly Color4 Lime0 = Color4Extensions.FromHex(@"ccff99"); - - /// - /// Equivalent to 's . - /// public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); - - /// - /// Equivalent to 's . - /// + public readonly Color4 Lime2 = Color4Extensions.FromHex(@"99eb47"); public readonly Color4 Lime3 = Color4Extensions.FromHex(@"7fcc33"); + public readonly Color4 Lime4 = Color4Extensions.FromHex(@"4c6b2e"); - /// - /// Equivalent to 's . - /// + public readonly Color4 Orange0 = Color4Extensions.FromHex(@"ffe699"); public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); + public readonly Color4 Orange2 = Color4Extensions.FromHex(@"ebc247"); + public readonly Color4 Orange3 = Color4Extensions.FromHex(@"cca633"); + public readonly Color4 Orange4 = Color4Extensions.FromHex(@"6b5c2e"); + + public readonly Color4 Red0 = Color4Extensions.FromHex(@"ff9b9b"); + public readonly Color4 Red1 = Color4Extensions.FromHex(@"ff6666"); + public readonly Color4 Red2 = Color4Extensions.FromHex(@"eb4747"); + public readonly Color4 Red3 = Color4Extensions.FromHex(@"cc3333"); + public readonly Color4 Red4 = Color4Extensions.FromHex(@"6b2e2e"); + + #endregion // Content Background public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28"); From 2592f0900d384e958cfbb6b99a638cb8089be134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:40:08 +0100 Subject: [PATCH 20/37] Add comments about `OverlayColourProvider` vs `OsuColour` distinction --- osu.Game/Graphics/OsuColour.cs | 5 +++++ osu.Game/Overlays/OverlayColourProvider.cs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 567d665e81..886ba7ef92 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -268,6 +268,11 @@ namespace osu.Game.Graphics // Reference: https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Asset%2FColours?node-id=1838%3A3 + // Note that the colours in this region are also defined in `OverlayColourProvider` as `Colour{0,1,2,3,4}`. + // The difference as to which should be used where comes down to context. + // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. + // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. + public readonly Color4 Pink0 = Color4Extensions.FromHex(@"ff99c7"); public readonly Color4 Pink1 = Color4Extensions.FromHex(@"ff66ab"); public readonly Color4 Pink2 = Color4Extensions.FromHex(@"eb4791"); diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index e7b3e6d873..730ca09411 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -25,6 +25,10 @@ namespace osu.Game.Overlays this.colourScheme = colourScheme; } + // Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`. + // The difference as to which should be used where comes down to context. + // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. + // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. public Color4 Colour1 => getColour(1, 0.7f); public Color4 Colour2 => getColour(0.8f, 0.6f); public Color4 Colour3 => getColour(0.6f, 0.5f); From 79ba37bbabc1fdd1acffcef16c4caac092066665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:40:39 +0100 Subject: [PATCH 21/37] Add `Colour0` to `OverlayColourProvider` --- osu.Game/Overlays/OverlayColourProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 730ca09411..c2ceef57e8 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -29,6 +29,7 @@ namespace osu.Game.Overlays // The difference as to which should be used where comes down to context. // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. + public Color4 Colour0 => getColour(1, 0.8f); public Color4 Colour1 => getColour(1, 0.7f); public Color4 Colour2 => getColour(0.8f, 0.6f); public Color4 Colour3 => getColour(0.6f, 0.5f); From ce0db9d4dbd7a0b030558002331dea4312be728a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:46:12 +0100 Subject: [PATCH 22/37] Remove all references to static `OverlayColourProvider`s --- .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 7 ++++++- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 7 +++++++ osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs | 2 +- osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index fb9e1c0420..51dad100c2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Game.Graphics; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; @@ -33,7 +35,10 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override Color4 GetStateColour() => OverlayColourProvider.Orange.Colour1; + [Resolved] + private OsuColour colours { get; set; } + + protected override Color4 GetStateColour() => colours.Orange1; } } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 9274cf20aa..52dfcad2cc 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -44,7 +44,14 @@ namespace osu.Game.Overlays.BeatmapListing }); Enabled.Value = true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + FinishTransforms(true); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs index ba78592ed2..21d1d1172c 100644 --- a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Orange.Colour2, + Colour = colours.Orange2 } } }; diff --git a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs index fdee0799ff..1be987cde2 100644 --- a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Blue.Colour1, + Colour = colours.Blue1 } } }; From 36a00c1ee238b13cfe857882110e1917cef5c49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:46:33 +0100 Subject: [PATCH 23/37] Remove static `OverlayColourProvider`s --- osu.Game/Overlays/OverlayColourProvider.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index c2ceef57e8..7bddb924a0 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -11,15 +11,6 @@ namespace osu.Game.Overlays { private readonly OverlayColourScheme colourScheme; - public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); - public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); - public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); - public static OverlayColourProvider Lime { get; } = new OverlayColourProvider(OverlayColourScheme.Lime); - public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); - public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); - public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); - public static OverlayColourProvider Plum { get; } = new OverlayColourProvider(OverlayColourScheme.Plum); - public OverlayColourProvider(OverlayColourScheme colourScheme) { this.colourScheme = colourScheme; From 2ded7d281b4b36e8d92ec12d72f1ba19ee33f935 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:17:19 +0900 Subject: [PATCH 24/37] Remove unused using statement --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a8b508829d..0e9968f27f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -30,7 +30,6 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; -using osu.Framework.Utils; namespace osu.Game.Online.Leaderboards { From 79408f6afcfd778e84c59ae329c73faf253dfa56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:30:58 +0900 Subject: [PATCH 25/37] Add xmldoc and clean up `ScoreboardTimeUtils` extension methods a touch --- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../BeatmapSet/Scores/ScoreboardTime.cs | 2 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 36 +++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 0e9968f27f..a9f03e5c9f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -395,7 +395,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index ceb446f310..1927e66edb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromHours(1)); + => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 86639c1034..4e079ff39f 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,15 +10,13 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - public static string FormatQuantity(string template, int quantity) - { - if (quantity <= 1) - return $@"{quantity}{template}"; - - return $@"{quantity}{template}s"; - } - - public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) { // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. // this is intended to be a best-effort, more legible approximation of that. @@ -36,23 +34,23 @@ namespace osu.Game.Utils return CommonStrings.TimeNow.ToString(); if (span.TotalMinutes < 1) - return FormatQuantity("sec", (int)span.TotalSeconds); + return formatQuantity("sec", (int)span.TotalSeconds); if (span.TotalHours < 1) - return FormatQuantity("min", (int)span.TotalMinutes); + return formatQuantity("min", (int)span.TotalMinutes); if (span.TotalDays < 1) - return FormatQuantity("hr", (int)span.TotalHours); + return formatQuantity("hr", (int)span.TotalHours); // this is where this gets more complicated because of how the calendar works. // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years // and test against cutoff dates to determine how many months/years to show. if (time > now.AddMonths(-1)) - return FormatQuantity("dy", (int)span.TotalDays); + return formatQuantity("dy", (int)span.TotalDays); for (int months = 1; months <= 11; ++months) { if (time > now.AddMonths(-(months + 1))) - return FormatQuantity("mo", months); + return formatQuantity("mo", months); } int years = 1; @@ -60,9 +58,17 @@ namespace osu.Game.Utils while (years < 20 && time <= now.AddYears(-(years + 1))) years += 1; if (years < 20) - return FormatQuantity("yr", years); + return formatQuantity("yr", years); return "never"; } + + private static string formatQuantity(string template, int quantity) + { + if (quantity <= 1) + return $@"{quantity}{template}"; + + return $@"{quantity}{template}s"; + } } } From c3b365cf6b79c8cfd618982fb163e6b7e72ed21e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 13:31:02 +0900 Subject: [PATCH 26/37] Scale classic score by hitobject count --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 15 ++++++++------- osu.Game/Scoring/ScoreManager.cs | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 79861c0ecc..42ab30d4d0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(mode, maxAchievableCombo, + return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); @@ -222,12 +222,11 @@ namespace osu.Game.Rulesets.Scoring /// Computes the total score. /// /// The to compute the total score in. - /// The maximum combo achievable in the beatmap. /// The accuracy percentage achieved by the player. - /// The proportion of achieved by the player. + /// The proportion of the max combo achieved by the player. /// Any statistics to be factored in. /// The total score. - public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, Dictionary statistics) + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) { switch (mode) { @@ -238,10 +237,12 @@ namespace osu.Game.Rulesets.Scoring return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: + int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. - double scaledStandardised = GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * (maxCombo + 1), 2) * 18; + double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; + return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; } } @@ -265,7 +266,7 @@ namespace osu.Game.Rulesets.Scoring computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; } - return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); + return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); } /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 532c6b42a3..963c4a77ca 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -184,7 +184,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.GetScore(mode, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + return (long)Math.Round(scoreProcessor.GetScore(mode, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } /// From fc1877b6faea3ad3bb9a476fc744d85dd6a463c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:42:26 +0900 Subject: [PATCH 27/37] Move to extension method and revert logic to match previous implementation --- osu.Game/Extensions/TimeDisplayExtensions.cs | 51 ++++++++++++++++++ .../Online/Leaderboards/LeaderboardScore.cs | 3 +- .../BeatmapSet/Scores/ScoreboardTime.cs | 4 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 52 ------------------- 4 files changed, 55 insertions(+), 55 deletions(-) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index dc05482a05..ee6f458398 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Extensions { @@ -47,5 +49,54 @@ namespace osu.Game.Extensions return new LocalisableFormattableString(timeSpan, @"mm\:ss"); } + + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) + { + var now = DateTime.Now; + var difference = now - time; + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (difference.TotalMinutes < 1) + return "sec".ToQuantity((int)difference.TotalSeconds); + if (difference.TotalHours < 1) + return "min".ToQuantity((int)difference.TotalMinutes); + if (difference.TotalDays < 1) + return "hr".ToQuantity((int)difference.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return months == 1 ? "1mo" : $"{months}mos"; + } + + int years = 1; + while (time <= now.AddYears(-(years + 1))) + years += 1; + return years == 1 ? "1yr" : $"{years}yrs"; + } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a9f03e5c9f..ddd9d9a2b2 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -395,7 +396,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index 1927e66edb..5018fb8c70 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Extensions; using osu.Game.Graphics; -using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); + => Date.ToShortRelativeTime(TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 4e079ff39f..ec222de018 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,58 +10,6 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - /// - /// Formats a provided date to a short relative string version for compact display. - /// - /// The time to be displayed. - /// A timespan denoting the time length beneath which "now" should be displayed. - /// A short relative string representing the input time. - public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) - { - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - var now = DateTime.Now; - var span = now - time; - - if (span < lowerCutoff) - return CommonStrings.TimeNow.ToString(); - - if (span.TotalMinutes < 1) - return formatQuantity("sec", (int)span.TotalSeconds); - if (span.TotalHours < 1) - return formatQuantity("min", (int)span.TotalMinutes); - if (span.TotalDays < 1) - return formatQuantity("hr", (int)span.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (time > now.AddMonths(-1)) - return formatQuantity("dy", (int)span.TotalDays); - - for (int months = 1; months <= 11; ++months) - { - if (time > now.AddMonths(-(months + 1))) - return formatQuantity("mo", months); - } - - int years = 1; - // Add upper bound to prevent a crash - while (years < 20 && time <= now.AddYears(-(years + 1))) - years += 1; - if (years < 20) - return formatQuantity("yr", years); - - return "never"; - } private static string formatQuantity(string template, int quantity) { From 3d0caa44c84f908a013eeac29bc126cfa20bacf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:43:30 +0900 Subject: [PATCH 28/37] Remove unused utils class --- osu.Game/Utils/ScoreboardTimeUtils.cs | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 osu.Game/Utils/ScoreboardTimeUtils.cs diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs deleted file mode 100644 index ec222de018..0000000000 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Game.Resources.Localisation.Web; - -#nullable enable - -namespace osu.Game.Utils -{ - public static class ScoreboardTimeUtils - { - - private static string formatQuantity(string template, int quantity) - { - if (quantity <= 1) - return $@"{quantity}{template}"; - - return $@"{quantity}{template}s"; - } - } -} From 656c58567dbe8068d7d4f7dfabcdf2b0818fbb87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 16:21:36 +0900 Subject: [PATCH 29/37] Add safeties to skip attempted import of the intro beatmap when osu! ruleset not present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In general running this import will not cause any critical failures, but the import itself *will* fail – and more loudly with the upcoming changes to `RulesetStore` (https://github.com/ppy/osu/pull/16890). Due to it being a loud failure, it will cause the notification overlay to display a parsing error, which can interrupt the flow of some tests. See test failure at https://github.com/ppy/osu/runs/5268848949?check_suite_focus=true as an example (video coverage at https://github.com/ppy/osu/pull/16890#issuecomment-1046542243). --- osu.Game/Screens/Menu/IntroScreen.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 98c4b15f7f..afe75c5ef7 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -19,6 +19,7 @@ using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -71,6 +72,9 @@ namespace osu.Game.Screens.Menu [CanBeNull] private readonly Func createNextScreen; + [Resolved] + private RulesetStore rulesets { get; set; } + /// /// Whether the is provided by osu! resources, rather than a user beatmap. /// Only valid during or after . @@ -117,7 +121,11 @@ namespace osu.Game.Screens.Menu // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. if (initialBeatmap == null) { - if (!loadThemedIntro()) + // Intro beatmaps are generally made using the osu! ruleset. + // It might not be present in test projects for other rulesets. + bool osuRulesetPresent = rulesets.GetRuleset(0) != null; + + if (!loadThemedIntro() && osuRulesetPresent) { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. From 2f6e65a9a268bf3c4b29a4005556cae95d524a31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 16:35:39 +0900 Subject: [PATCH 30/37] Gracefully handle undefined `DateTimeOffset` values Only seems to happen in tests, but best to safeguard against this regardless. --- osu.Game/Extensions/TimeDisplayExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index ee6f458398..54af6a5942 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -58,6 +58,9 @@ namespace osu.Game.Extensions /// A short relative string representing the input time. public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) { + if (time == default) + return "-"; + var now = DateTime.Now; var difference = now - time; From c466d6df94e741a5def1a4395a5d6d03b29240c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 17:19:35 +0900 Subject: [PATCH 31/37] Ensure to not multiply by 0 --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 42ab30d4d0..d5a5aa4592 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -239,6 +239,10 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. + if (totalHitObjects == 0) + totalHitObjects = 1; + // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; From 1737128334db2da3095f52e5fbcbb1c3fd169b8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:45:57 +0900 Subject: [PATCH 32/37] Allow room category to be copied even if `Spotlight` I remember that this conditional copy was added to support making copies of spotlight rooms without carrying across the `Spotlight` type, but in testing this is already handled web side to the point that it's not required. The rationale for allowing the copy is that this method is used for tests, where it was not being copied correctly from the input as expected (used at https://github.com/ppy/osu/blob/bdc3b76df0b58406796e2b08db13be7f2140fa7e/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs#L38). --- osu.Game/Online/Rooms/Room.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index bbe854f2dd..a328f8e8c0 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -168,8 +168,7 @@ namespace osu.Game.Online.Rooms RoomID.Value = other.RoomID.Value; Name.Value = other.Name.Value; - if (other.Category.Value != RoomCategory.Spotlight) - Category.Value = other.Category.Value; + Category.Value = other.Category.Value; if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) Host.Value = other.Host.Value; From ab8b502709f5da93709bcfc0f26a6e0053a9ccdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:59:51 +0900 Subject: [PATCH 33/37] Add test coverage of spotlights being at the top of the listing --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index c3d5f7ec23..9a66a2b05c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -44,15 +44,20 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddStep("add rooms", () => RoomManager.AddRooms(3)); + AddStep("add rooms", () => RoomManager.AddRooms(5)); - AddAssert("has 3 rooms", () => container.Rooms.Count == 3); - AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault())); - AddAssert("has 2 rooms", () => container.Rooms.Count == 2); + AddAssert("has 5 rooms", () => container.Rooms.Count == 5); + + AddAssert("all spotlights at top", () => container.Rooms + .SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight) + .All(r => r.Room.Category.Value == RoomCategory.Normal)); + + AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault(r => r.RoomID.Value == 0))); + AddAssert("has 4 rooms", () => container.Rooms.Count == 4); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); AddStep("select first room", () => container.Rooms.First().TriggerClick()); - AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First())); + AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); } [Test] From 02a85005004ae66dc4477bfd38932badc16ec894 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:59:09 +0900 Subject: [PATCH 34/37] Ensure spotlights always show at the top of the lounge listing As proposed at https://github.com/ppy/osu/discussions/16936. Spotlights are intended to have focus, so let's make sure they are the first thing the user sees for the time being. --- .../Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 9f917c978c..3260427192 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -124,7 +124,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateSorting() { foreach (var room in roomFlow) - roomFlow.SetLayoutPosition(room, -(room.Room.RoomID.Value ?? 0)); + { + roomFlow.SetLayoutPosition(room, room.Room.Category.Value == RoomCategory.Spotlight + // Always show spotlight playlists at the top of the listing. + ? float.MinValue + : -(room.Room.RoomID.Value ?? 0)); + } } protected override bool OnClick(ClickEvent e) From 46b408be75b21c3fe872227c298d696851150d3e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 17:59:50 +0900 Subject: [PATCH 35/37] Update tests to match new behaviour --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index f0d9ece06f..c6e7988543 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] [TestCase(ScoringMode.Standardised, HitResult.Ok, 800_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] - [TestCase(ScoringMode.Classic, HitResult.Meh, 41)] - [TestCase(ScoringMode.Classic, HitResult.Ok, 46)] - [TestCase(ScoringMode.Classic, HitResult.Great, 72)] + [TestCase(ScoringMode.Classic, HitResult.Meh, 20)] + [TestCase(ScoringMode.Classic, HitResult.Ok, 23)] + [TestCase(ScoringMode.Classic, HitResult.Great, 36)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { scoreProcessor.Mode.Value = scoringMode; @@ -86,17 +86,17 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 68)] - [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 81)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 109)] - [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 149)] - [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 149)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 9)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 15)] + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 86)] + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 104)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 140)] + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 190)] + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 190)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 18)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 31)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 149)] - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 18)] - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 18)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 12)] + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 36)] + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 36)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -128,8 +128,8 @@ namespace osu.Game.Tests.Rulesets.Scoring /// [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 69)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 60)] // (((3 * 0 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 34)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 30)] public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable From 655b23f408c483afb04916e16bfd4b15ebfbaf3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:46:31 +0900 Subject: [PATCH 36/37] Update playlist room display to a three column layout Similar to the changes made to multiplayer. --- .../Playlists/PlaylistsRoomSubScreen.cs | 227 ++++++++++-------- 1 file changed, 125 insertions(+), 102 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 542851cb0f..338a9c856f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -69,132 +69,155 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Room.MaxAttempts.BindValueChanged(attempts => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); } - protected override Drawable CreateMainContent() => new GridContainer + protected override Drawable CreateMainContent() => new Container { RelativeSizeAxes = Axes.Both, - Content = new[] + Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, + Child = new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - new Container + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - Child = new GridContainer + // Playlist items column + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedPlaylistHeader(), }, + new Drawable[] + { + new DrawableRoomPlaylist + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = Room.Playlist }, + SelectedItem = { BindTarget = SelectedItem }, + AllowSelection = true, + AllowShowingResults = true, + RequestResults = item => + { + Debug.Assert(RoomId.Value != null); + ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + } + }, + // Spacer + null, + // Middle column (mods and leaderboard) + new GridContainer { RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new OverlinedPlaylistHeader(), }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new UserModSelectButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } + } + }, + }, new Drawable[] { - new DrawableRoomPlaylist + progressSection = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, - SelectedItem = { BindTarget = SelectedItem }, - AllowSelection = true, - AllowShowingResults = true, - RequestResults = item => + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + new OverlinedHeader("Progress"), + new RoomLocalUserInfo(), } - } + }, }, + new Drawable[] + { + new OverlinedHeader("Leaderboard") + }, + new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Chat") }, + new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(), } - } - }, - null, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new[] - { - UserModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Children = new Drawable[] - { - new OverlinedHeader("Extra mods"), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new UserModSelectButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 90, - Text = "Select", - Action = ShowUserModSelect, - }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = UserMods, - Scale = new Vector2(0.8f), - }, - } - } - } - }, - }, - new Drawable[] - { - progressSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new OverlinedHeader("Progress"), - new RoomLocalUserInfo(), - } - }, - }, - new Drawable[] - { - new OverlinedHeader("Leaderboard") - }, - new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, - new Drawable[] { new OverlinedHeader("Chat"), }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120), - } }, }, - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), - new Dimension(), - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), } }; From 98c008b95f49ac3c1676619aa9195cce4ad46c54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:48:39 +0900 Subject: [PATCH 37/37] Fix test failures due to order change --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 2 +- osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 9a66a2b05c..93cd281bc5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddStep("add rooms", () => RoomManager.AddRooms(5)); + AddStep("add rooms", () => RoomManager.AddRooms(5, withSpotlightRooms: true)); AddAssert("has 5 rooms", () => container.Rooms.Count == 5); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 8dfd969c51..6abcb2924c 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay base.JoinRoom(room, password, onSuccess, onError); } - public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false) + public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) { for (int i = 0; i < count; i++) { @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay Name = { Value = $@"Room {currentRoomId}" }, Host = { Value = new APIUser { Username = @"Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, + Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, }; if (withPassword)