// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; using osuTK.Graphics; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Tests.Visual.Online { public partial class TestSceneScoresContainer : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); private TestScoresContainer scoresContainer; [SetUpSteps] public void SetUp() => Schedule(() => { Child = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Both, Width = 0.8f, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, scoresContainer = new TestScoresContainer { Beatmap = { Value = CreateAPIBeatmap() } } } }; }); [Test] public void TestNoUserBest() { AddStep("Scores with no user best", () => { var allScores = createScores(); allScores.UserScore = null; scoresContainer.Scores = allScores; }); AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); AddAssert("no user best displayed", () => scoresContainer.ChildrenOfType().Count() == 1); AddStep("Load null scores", () => scoresContainer.Scores = null); AddUntilStep("wait for scores not displayed", () => !scoresContainer.ChildrenOfType().Any()); AddAssert("no best score displayed", () => !scoresContainer.ChildrenOfType().Any()); AddStep("Load only one score", () => { var allScores = createScores(); allScores.Scores.RemoveRange(1, allScores.Scores.Count - 1); scoresContainer.Scores = allScores; }); AddUntilStep("wait for scores not displayed", () => scoresContainer.ChildrenOfType().Count() == 1); AddAssert("no best score displayed", () => scoresContainer.ChildrenOfType().Count() == 1); } [Test] public void TestHitResultsWithSameNameAreGrouped() { AddStep("Load scores without user best", () => { var allScores = createScores(); allScores.UserScore = null; scoresContainer.Scores = allScores; }); AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); AddAssert("only one column for slider end", () => { ScoreTable scoreTable = scoresContainer.ChildrenOfType().First(); return scoreTable.Columns.Count(c => c.Header.Equals("slider end")) == 1; }); AddAssert("all rows show non-zero slider ends", () => { ScoreTable scoreTable = scoresContainer.ChildrenOfType().First(); int sliderEndColumnIndex = Array.FindIndex(scoreTable.Columns, c => c != null && c.Header.Equals("slider end")); bool sliderEndFilledInEachRow = true; for (int i = 0; i < scoreTable.Content?.GetLength(0); i++) { switch (scoreTable.Content[i, sliderEndColumnIndex]) { case OsuSpriteText text: if (text.Text.Equals(0.0d.ToLocalisableString(@"N0"))) sliderEndFilledInEachRow = false; break; default: sliderEndFilledInEachRow = false; break; } } return sliderEndFilledInEachRow; }); } [Test] public void TestUserBest() { AddStep("Load scores with personal best", () => { var allScores = createScores(); allScores.UserScore = createUserBest(); scoresContainer.Scores = allScores; }); AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); AddStep("Load scores with personal best FC", () => { var allScores = createScores(); allScores.UserScore = createUserBest(); allScores.UserScore.Score.Accuracy = 1; scoresContainer.Beatmap.Value.MaxCombo = allScores.UserScore.Score.MaxCombo = 1337; scoresContainer.Scores = allScores; }); AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); AddStep("Load scores with personal best (null position)", () => { var allScores = createScores(); var userBest = createUserBest(); userBest.Position = null; allScores.UserScore = userBest; scoresContainer.Scores = allScores; }); AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); AddStep("Load scores with personal best (first place)", () => { var allScores = createScores(); allScores.UserScore = new APIScoreWithPosition { Score = allScores.Scores.First(), Position = 1, }; scoresContainer.Scores = allScores; }); AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 1); AddStep("Scores with no user best", () => { var allScores = createScores(); allScores.UserScore = null; scoresContainer.Scores = allScores; }); AddUntilStep("best score not displayed", () => scoresContainer.ChildrenOfType().Count() == 1); } [Test] public void TestUnprocessedPP() { AddStep("Load scores with unprocessed PP", () => { var allScores = createScores(); allScores.Scores[0].PP = null; allScores.UserScore = createUserBest(); allScores.UserScore.Score.PP = null; scoresContainer.Scores = allScores; }); } [Test] public void TestUnrankedPP() { AddStep("Load scores with unranked PP", () => { var allScores = createScores(); allScores.Scores[0].Ranked = false; allScores.UserScore = createUserBest(); allScores.UserScore.Score.Ranked = false; scoresContainer.Scores = allScores; }); } private ulong onlineID = 1; private APIScoresCollection createScores() { var scores = new APIScoresCollection { Scores = new List { new SoloScoreInfo { EndedAt = DateTimeOffset.Now, ID = onlineID++, User = new APIUser { Id = 6602580, Username = @"waaiiru", CountryCode = CountryCode.ES, }, Mods = new[] { new APIMod { Acronym = new OsuModDoubleTime().Acronym }, new APIMod { Acronym = new OsuModHidden().Acronym }, new APIMod { Acronym = new OsuModFlashlight().Acronym }, new APIMod { Acronym = new OsuModHardRock().Acronym }, }, Rank = ScoreRank.XH, PP = 200, MaxCombo = 1234, TotalScore = 1234567890, Accuracy = 1, Ranked = true, }, new SoloScoreInfo { EndedAt = DateTimeOffset.Now, ID = onlineID++, User = new APIUser { Id = 4608074, Username = @"Skycries", CountryCode = CountryCode.BR, }, Mods = new[] { new APIMod { Acronym = new OsuModDoubleTime().Acronym }, new APIMod { Acronym = new OsuModHidden().Acronym }, new APIMod { Acronym = new OsuModFlashlight().Acronym }, }, Rank = ScoreRank.S, PP = 190, MaxCombo = 1234, TotalScore = 1234789, Accuracy = 0.9997, Ranked = true, }, new SoloScoreInfo { EndedAt = DateTimeOffset.Now, ID = onlineID++, User = new APIUser { Id = 1014222, Username = @"eLy", CountryCode = CountryCode.JP, }, Mods = new[] { new APIMod { Acronym = new OsuModDoubleTime().Acronym }, new APIMod { Acronym = new OsuModHidden().Acronym }, }, Rank = ScoreRank.B, PP = 180, MaxCombo = 1234, TotalScore = 12345678, Accuracy = 0.9854, Ranked = true, }, new SoloScoreInfo { EndedAt = DateTimeOffset.Now, ID = onlineID++, User = new APIUser { Id = 1541390, Username = @"Toukai", CountryCode = CountryCode.CA, }, Mods = new[] { new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, Rank = ScoreRank.C, PP = 170, MaxCombo = 1234, TotalScore = 1234567, Accuracy = 0.8765, Ranked = true, }, new SoloScoreInfo { EndedAt = DateTimeOffset.Now, ID = onlineID++, User = new APIUser { Id = 7151382, Username = @"Mayuri Hana", CountryCode = CountryCode.TH, }, Rank = ScoreRank.D, PP = 160, MaxCombo = 1234, TotalScore = 123456, Accuracy = 0.6543, Ranked = true, }, } }; const int initial_great_count = 2000; const int initial_tick_count = 100; const int initial_slider_end_count = 500; int greatCount = initial_great_count; int tickCount = initial_tick_count; int sliderEndCount = initial_slider_end_count; foreach (var (score, index) in scores.Scores.Select((s, i) => (s, i))) { HitResult sliderEndResult = index % 2 == 0 ? HitResult.SliderTailHit : HitResult.SmallTickHit; score.Statistics = new Dictionary { { HitResult.Great, greatCount }, { HitResult.LargeTickHit, tickCount }, { HitResult.Ok, RNG.Next(100) }, { HitResult.Meh, RNG.Next(100) }, { HitResult.Miss, initial_great_count - greatCount }, { HitResult.LargeTickMiss, initial_tick_count - tickCount }, { sliderEndResult, sliderEndCount }, }; // Some hit results, including SliderTailHit and SmallTickHit, are only displayed // when the maximum number is known score.MaximumStatistics = new Dictionary { { sliderEndResult, initial_slider_end_count }, }; greatCount -= 100; tickCount -= RNG.Next(1, 5); sliderEndCount -= 20; } return scores; } private APIScoreWithPosition createUserBest() => new APIScoreWithPosition { Score = new SoloScoreInfo { EndedAt = DateTimeOffset.Now, ID = onlineID++, User = new APIUser { Id = 7151382, Username = @"Mayuri Hana", CountryCode = CountryCode.TH, }, Rank = ScoreRank.D, PP = 160, MaxCombo = 1234, TotalScore = 123456, Accuracy = 0.6543, Ranked = true, }, Position = 1337, }; private partial class TestScoresContainer : ScoresContainer { public new APIScoresCollection Scores { set => base.Scores = value; } } } }