diff --git a/osu.Android.props b/osu.Android.props
index 3ec12089ad..f19a33ba75 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
+
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index 81d89359e0..9ac7838821 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -95,7 +95,6 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(decodedAfterEncode, Is.Not.Null);
Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username));
- Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID));
Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset));
Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore));
Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo));
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
index 55378043e6..8ba3d1a6c7 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -16,7 +16,8 @@ namespace osu.Game.Tests.NonVisual.Filtering
{
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
{
- Ruleset = new RulesetInfo { OnlineID = 5 },
+ Ruleset = new RulesetInfo { OnlineID = 0 },
+ RulesetID = 0,
StarRating = 4.0d,
BaseDifficulty = new BeatmapDifficulty
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
index 25808d307d..cf5aadde6d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoSubmissionOnResultsWithNoToken()
{
- prepareTokenResponse(false);
+ prepareTestAPI(false);
createPlayerTest();
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionOnResults()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest();
@@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionForDifferentRuleset()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(createRuleset: () => new TaikoRuleset());
@@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionForConvertedBeatmap()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(createRuleset: () => new ManiaRuleset(), createBeatmap: _ => createTestBeatmap(new OsuRuleset().RulesetInfo));
@@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoSubmissionOnExitWithNoToken()
{
- prepareTokenResponse(false);
+ prepareTestAPI(false);
createPlayerTest();
@@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoSubmissionOnEmptyFail()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(true);
@@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionOnFail()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(true);
@@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoSubmissionOnEmptyExit()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest();
@@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionOnExit()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest();
@@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSubmissionOnExitDuringImport()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest();
AddStep("block imports", () => Player.AllowImportCompletion.Wait());
@@ -226,13 +226,13 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("exit", () => Player.Exit());
AddStep("allow import to proceed", () => Player.AllowImportCompletion.Release(1));
- AddAssert("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null);
+ AddUntilStep("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null);
}
[Test]
public void TestNoSubmissionOnLocalBeatmap()
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(false, r =>
{
@@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(10)]
public void TestNoSubmissionOnCustomRuleset(int? rulesetId)
{
- prepareTokenResponse(true);
+ prepareTestAPI(true);
createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { OnlineID = rulesetId ?? -1 } });
@@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}));
}
- private void prepareTokenResponse(bool validToken)
+ private void prepareTestAPI(bool validToken)
{
AddStep("Prepare test API", () =>
{
@@ -289,6 +289,31 @@ namespace osu.Game.Tests.Visual.Gameplay
else
tokenRequest.TriggerFailure(new APIException("something went wrong!", null));
return true;
+
+ case SubmitSoloScoreRequest submissionRequest:
+ if (validToken)
+ {
+ var requestScore = submissionRequest.Score;
+
+ submissionRequest.TriggerSuccess(new MultiplayerScore
+ {
+ ID = 1234,
+ User = dummyAPI.LocalUser.Value,
+ Rank = requestScore.Rank,
+ TotalScore = requestScore.TotalScore,
+ Accuracy = requestScore.Accuracy,
+ MaxCombo = requestScore.MaxCombo,
+ Mods = requestScore.Mods,
+ Statistics = requestScore.Statistics,
+ Passed = requestScore.Passed,
+ EndedAt = DateTimeOffset.Now,
+ Position = 1
+ });
+
+ return true;
+ }
+
+ break;
}
return false;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
index a5744f9986..ad60ac824d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
@@ -1,13 +1,25 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
using System;
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions.ObjectExtensions;
+using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
+using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
@@ -83,16 +95,60 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
}
- private void addItem(Func beatmap)
+ [Test]
+ public void TestCorrectRulesetSelectedAfterNewItemAdded()
{
+ addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo);
+ AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
+
+ AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle);
+ ClickButtonWhenEnabled();
+
+ AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
+ ClickButtonWhenEnabled();
+
+ AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded);
+ AddAssert("ruleset is correct", () => ((Player)CurrentScreen).Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
+ AddStep("exit player", () => CurrentScreen.Exit());
+ }
+
+ [Test]
+ public void TestCorrectModsSelectedAfterNewItemAdded()
+ {
+ addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() });
+ AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
+
+ AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle);
+ ClickButtonWhenEnabled();
+
+ AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
+ ClickButtonWhenEnabled();
+
+ AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded);
+ AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any());
+ AddStep("exit player", () => CurrentScreen.Exit());
+ }
+
+ private void addItem(Func beatmap, RulesetInfo? ruleset = null, IReadOnlyList? mods = null)
+ {
+ Screens.Select.SongSelect? songSelect = null;
+
AddStep("click add button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType().Single());
InputManager.Click(MouseButton.Left);
});
- AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
- AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
+ AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null);
+ AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded);
+
+ if (ruleset != null)
+ AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset);
+
+ if (mods != null)
+ AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods);
+
+ AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 24fc3ac5b9..3d8c5298dc 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Extensions;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
@@ -23,6 +24,8 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
@@ -439,6 +442,84 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID);
}
+ [Test]
+ public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect()
+ {
+ createRoom(() => new Room
+ {
+ Name = { Value = "Test Room" },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ }
+ }
+ });
+
+ pressReadyButton();
+
+ AddStep("Enter song select", () =>
+ {
+ var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
+ ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId);
+ });
+
+ AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true);
+
+ AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID);
+
+ AddStep("Switch ruleset", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Ruleset.Value = new CatchRuleset().RulesetInfo);
+
+ AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != client.Room?.Playlist.First().RulesetID);
+
+ AddStep("start match externally", () => client.StartMatch());
+
+ AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player);
+
+ AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID);
+ }
+
+ [Test]
+ public void TestPlayStartsWithCorrectModsWhileAtSongSelect()
+ {
+ createRoom(() => new Room
+ {
+ Name = { Value = "Test Room" },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ }
+ }
+ });
+
+ pressReadyButton();
+
+ AddStep("Enter song select", () =>
+ {
+ var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
+ ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId);
+ });
+
+ AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true);
+
+ AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym)));
+
+ AddStep("Switch required mods", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Mods.Value = new Mod[] { new OsuModDoubleTime() });
+
+ AddAssert("Mods don't match current item", () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym)));
+
+ AddStep("start match externally", () => client.StartMatch());
+
+ AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player);
+
+ AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym)));
+ }
+
[Test]
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs
index ee109189c7..35e219f839 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs
@@ -1,38 +1,27 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Rankings.Tables;
using osu.Framework.Graphics;
-using osu.Game.Online.API.Requests;
-using osu.Game.Rulesets;
using System.Threading;
-using osu.Game.Online.API;
-using osu.Game.Rulesets.Osu;
-using osu.Game.Rulesets.Mania;
-using osu.Game.Rulesets.Taiko;
-using osu.Game.Rulesets.Catch;
using osu.Framework.Allocation;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
-using osu.Game.Overlays.Rankings;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneRankingsTables : OsuTestScene
{
- protected override bool UseOnlineAPI => true;
-
- [Resolved]
- private IAPIProvider api { get; set; }
-
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
private readonly BasicScrollContainer scrollFlow;
private readonly LoadingLayer loading;
private CancellationTokenSource cancellationToken;
- private APIRequest request;
public TestSceneRankingsTables()
{
@@ -53,73 +42,120 @@ namespace osu.Game.Tests.Visual.Online
{
base.LoadComplete();
- AddStep("Osu performance", () => createPerformanceTable(new OsuRuleset().RulesetInfo, null));
- AddStep("Mania scores", () => createScoreTable(new ManiaRuleset().RulesetInfo));
- AddStep("Taiko country scores", () => createCountryTable(new TaikoRuleset().RulesetInfo));
- AddStep("Catch US performance page 10", () => createPerformanceTable(new CatchRuleset().RulesetInfo, "US", 10));
- AddStep("Osu spotlight table (chart 271)", () => createSpotlightTable(new OsuRuleset().RulesetInfo, 271));
+ AddStep("User performance", createPerformanceTable);
+ AddStep("User scores", createScoreTable);
+ AddStep("Country scores", createCountryTable);
}
- private void createCountryTable(RulesetInfo ruleset, int page = 1)
+ private void createCountryTable()
{
onLoadStarted();
- request = new GetCountryRankingsRequest(ruleset, page);
- ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() =>
+ var countries = new List
{
- var table = new CountriesTable(page, rankings.Countries);
- loadTable(table);
- });
+ new CountryStatistics
+ {
+ Country = new Country { FlagName = "US", FullName = "United States" },
+ FlagName = "US",
+ ActiveUsers = 2_972_623,
+ PlayCount = 3_086_515_743,
+ RankedScore = 449_407_643_332_546,
+ Performance = 371_974_024
+ },
+ new CountryStatistics
+ {
+ Country = new Country { FlagName = "RU", FullName = "Russian Federation" },
+ FlagName = "RU",
+ ActiveUsers = 1_609_989,
+ PlayCount = 1_637_052_841,
+ RankedScore = 221_660_827_473_004,
+ Performance = 163_426_476
+ }
+ };
- api.Queue(request);
+ var table = new CountriesTable(1, countries);
+ loadTable(table);
}
- private void createPerformanceTable(RulesetInfo ruleset, string country, int page = 1)
+ private static List createUserStatistics() => new List
+ {
+ new UserStatistics
+ {
+ User = new APIUser
+ {
+ Username = "first active user",
+ Country = new Country { FlagName = "JP" },
+ Active = true,
+ },
+ Accuracy = 0.9972,
+ PlayCount = 233_215,
+ TotalScore = 983_231_234_656,
+ RankedScore = 593_231_345_897,
+ PP = 23_934,
+ GradesCount = new UserStatistics.Grades
+ {
+ SS = 35_132,
+ S = 23_345,
+ A = 12_234
+ }
+ },
+ new UserStatistics
+ {
+ User = new APIUser
+ {
+ Username = "inactive user",
+ Country = new Country { FlagName = "AU" },
+ Active = false,
+ },
+ Accuracy = 0.9831,
+ PlayCount = 195_342,
+ TotalScore = 683_231_234_656,
+ RankedScore = 393_231_345_897,
+ PP = 20_934,
+ GradesCount = new UserStatistics.Grades
+ {
+ SS = 32_132,
+ S = 20_345,
+ A = 9_234
+ }
+ },
+ new UserStatistics
+ {
+ User = new APIUser
+ {
+ Username = "second active user",
+ Country = new Country { FlagName = "PL" },
+ Active = true,
+ },
+ Accuracy = 0.9584,
+ PlayCount = 100_903,
+ TotalScore = 97_242_983_434,
+ RankedScore = 3_156_345_897,
+ PP = 9_568,
+ GradesCount = new UserStatistics.Grades
+ {
+ SS = 13_152,
+ S = 24_375,
+ A = 9_960
+ }
+ },
+ };
+
+ private void createPerformanceTable()
{
onLoadStarted();
-
- request = new GetUserRankingsRequest(ruleset, country: country, page: page);
- ((GetUserRankingsRequest)request).Success += rankings => Schedule(() =>
- {
- var table = new PerformanceTable(page, rankings.Users);
- loadTable(table);
- });
-
- api.Queue(request);
+ loadTable(new PerformanceTable(1, createUserStatistics()));
}
- private void createScoreTable(RulesetInfo ruleset, int page = 1)
+ private void createScoreTable()
{
onLoadStarted();
-
- request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page);
- ((GetUserRankingsRequest)request).Success += rankings => Schedule(() =>
- {
- var table = new ScoresTable(page, rankings.Users);
- loadTable(table);
- });
-
- api.Queue(request);
- }
-
- private void createSpotlightTable(RulesetInfo ruleset, int spotlight)
- {
- onLoadStarted();
-
- request = new GetSpotlightRankingsRequest(ruleset, spotlight, RankingsSortCriteria.All);
- ((GetSpotlightRankingsRequest)request).Success += rankings => Schedule(() =>
- {
- var table = new ScoresTable(1, rankings.Users);
- loadTable(table);
- });
-
- api.Queue(request);
+ loadTable(new ScoresTable(1, createUserStatistics()));
}
private void onLoadStarted()
{
loading.Show();
- request?.Cancel();
cancellationToken?.Cancel();
cancellationToken = new CancellationTokenSource();
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs b/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs
new file mode 100644
index 0000000000..7e33b5240c
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs
@@ -0,0 +1,57 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Timing;
+using osu.Game.Overlays.BeatmapSet.Scores;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneScoreboardTime : OsuTestScene
+ {
+ private StopwatchClock stopwatch;
+
+ [Test]
+ public void TestVariousUnits()
+ {
+ AddStep("create various scoreboard times", () => Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Clock = new FramedClock(stopwatch = new StopwatchClock()), // prevent time from naturally elapsing.
+ Direction = FillDirection.Vertical,
+ ChildrenEnumerable = testCases.Select(dateTime => new ScoreboardTime(dateTime, 24).With(time => time.Anchor = time.Origin = Anchor.TopCentre))
+ });
+
+ AddStep("start stopwatch", () => stopwatch.Start());
+ }
+
+ private static IEnumerable testCases => new[]
+ {
+ DateTimeOffset.Now,
+ DateTimeOffset.Now.AddSeconds(-1),
+ DateTimeOffset.Now.AddSeconds(-25),
+ DateTimeOffset.Now.AddSeconds(-59),
+ DateTimeOffset.Now.AddMinutes(-1),
+ DateTimeOffset.Now.AddMinutes(-25),
+ DateTimeOffset.Now.AddMinutes(-59),
+ DateTimeOffset.Now.AddHours(-1),
+ DateTimeOffset.Now.AddHours(-13),
+ DateTimeOffset.Now.AddHours(-23),
+ DateTimeOffset.Now.AddDays(-1),
+ DateTimeOffset.Now.AddDays(-6),
+ DateTimeOffset.Now.AddDays(-16),
+ DateTimeOffset.Now.AddMonths(-1),
+ DateTimeOffset.Now.AddMonths(-11),
+ DateTimeOffset.Now.AddYears(-1),
+ DateTimeOffset.Now.AddYears(-5)
+ };
+ }
+}
diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
index 952eb72bf4..03252e3be6 100644
--- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get();
FileBasedIPC ipc = null;
- WaitForOrAssert(() => (ipc = osu.Dependencies.Get() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time");
+ WaitForOrAssert(() => (ipc = osu.Dependencies.Get() as FileBasedIPC)?.IsLoaded == true, @"ipc could not be populated in a reasonable amount of time");
Assert.True(ipc.SetIPCLocation(testStableInstallDirectory));
Assert.True(storage.AllTournaments.Exists("stable.json"));
diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs
index e5177823ba..e09f046421 100644
--- a/osu.Game/Database/RealmObjectExtensions.cs
+++ b/osu.Game/Database/RealmObjectExtensions.cs
@@ -26,6 +26,9 @@ namespace osu.Game.Database
///
/// Create a detached copy of the each item in the collection.
///
+ ///
+ /// Items which are already detached (ie. not managed by realm) will not be modified.
+ ///
/// A list of managed s to detach.
/// The type of object.
/// A list containing non-managed copies of provided items.
@@ -42,6 +45,9 @@ namespace osu.Game.Database
///
/// Create a detached copy of the item.
///
+ ///
+ /// If the item if already detached (ie. not managed by realm) it will not be detached again and the original instance will be returned. This allows this method to be potentially called at multiple levels while only incurring the clone overhead once.
+ ///
/// The managed to detach.
/// The type of object.
/// A non-managed copy of provided item. Will return the provided item if already detached.
diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs
index 99cf5ceff5..78ebddb2e6 100644
--- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs
+++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs
@@ -12,17 +12,17 @@ namespace osu.Game.Online.Solo
{
public class SubmitSoloScoreRequest : APIRequest
{
+ public readonly SubmittableScore Score;
+
private readonly long scoreId;
private readonly int beatmapId;
- private readonly SubmittableScore score;
-
public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo)
{
this.beatmapId = beatmapId;
this.scoreId = scoreId;
- score = new SubmittableScore(scoreInfo);
+ Score = new SubmittableScore(scoreInfo);
}
protected override WebRequest CreateWebRequest()
@@ -33,7 +33,7 @@ namespace osu.Game.Online.Solo
req.Method = HttpMethod.Put;
req.Timeout = 30000;
- req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
+ req.AddRaw(JsonConvert.SerializeObject(Score, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}));
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
index 018faf2011..2c78fa264e 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
@@ -128,6 +128,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
if (showPerformancePoints)
columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeaderspp, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30)));
+ columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersTime, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)));
columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersMods, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)));
return columns.ToArray();
@@ -202,6 +203,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
});
}
+ content.Add(new ScoreboardTime(score.Date, text_size)
+ {
+ Margin = new MarginPadding { Right = 10 }
+ });
+
content.Add(new FillFlowContainer
{
Direction = FillDirection.Horizontal,
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs
new file mode 100644
index 0000000000..ff1d3490b4
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs
@@ -0,0 +1,56 @@
+// 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 Humanizer;
+using osu.Game.Graphics;
+using osu.Game.Resources.Localisation.Web;
+
+namespace osu.Game.Overlays.BeatmapSet.Scores
+{
+ public class ScoreboardTime : DrawableDate
+ {
+ public ScoreboardTime(DateTimeOffset date, float textSize = OsuFont.DEFAULT_FONT_SIZE, bool italic = true)
+ : base(date, textSize, italic)
+ {
+ }
+
+ 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";
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs
index 6e6230f958..fd69b6c80a 100644
--- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs
+++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs
@@ -54,13 +54,15 @@ namespace osu.Game.Overlays.Rankings.Tables
Spacing = new Vector2(0, row_spacing),
});
- rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground { Height = row_height }));
+ rankings.ForEach(s => backgroundFlow.Add(CreateRowBackground(s)));
Columns = mainHeaders.Concat(CreateAdditionalHeaders()).Cast().ToArray();
- Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular();
+ Content = rankings.Select((s, i) => CreateRowContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular();
}
- private Drawable[] createContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray();
+ protected virtual Drawable CreateRowBackground(TModel item) => new TableRowBackground { Height = row_height };
+
+ protected virtual Drawable[] CreateRowContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray();
private static RankingsTableColumn[] mainHeaders => new[]
{
diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs
index cc2ef55a2b..5d150c9535 100644
--- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs
+++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs
@@ -24,6 +24,31 @@ namespace osu.Game.Overlays.Rankings.Tables
protected virtual IEnumerable GradeColumns => new List { RankingsStrings.Statss, RankingsStrings.Stats, RankingsStrings.Stata };
+ protected override Drawable CreateRowBackground(UserStatistics item)
+ {
+ var background = base.CreateRowBackground(item);
+
+ // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23
+ if (!item.User.Active)
+ background.Alpha = 0.5f;
+
+ return background;
+ }
+
+ protected override Drawable[] CreateRowContent(int index, UserStatistics item)
+ {
+ var content = base.CreateRowContent(index, item);
+
+ // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23
+ if (!item.User.Active)
+ {
+ foreach (var d in content)
+ d.Alpha = 0.5f;
+ }
+
+ return content;
+ }
+
protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[]
{
new RankingsTableColumn(RankingsStrings.StatAccuracy, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs
index 8738d36bf4..67f1dacec4 100644
--- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs
+++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Globalization;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -9,6 +10,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings;
+using osu.Game.Utils;
namespace osu.Game.Screens.Edit.Timing
{
@@ -66,7 +68,8 @@ namespace osu.Game.Screens.Edit.Timing
Current.BindValueChanged(val =>
{
- textBox.Text = val.NewValue.ToString();
+ decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo);
+ textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}");
}, true);
}
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index a0e7e8de87..c31239616c 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -300,6 +300,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
updateWorkingBeatmap();
beginHandlingTrack();
Scheduler.AddOnce(UpdateMods);
+ Scheduler.AddOnce(updateRuleset);
}
public override bool OnExiting(IScreen next)
@@ -353,8 +354,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
.ToList();
UpdateMods();
-
- Ruleset.Value = rulesets.GetRuleset(selected.RulesetID);
+ updateRuleset();
if (!selected.AllowedMods.Any())
{
@@ -387,6 +387,14 @@ namespace osu.Game.Screens.OnlinePlay.Match
Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList();
}
+ private void updateRuleset()
+ {
+ if (SelectedItem.Value == null || !this.IsCurrentScreen())
+ return;
+
+ Ruleset.Value = rulesets.GetRuleset(SelectedItem.Value.RulesetID);
+ }
+
private void beginHandlingTrack()
{
Beatmap.BindValueChanged(applyLoopingToTrack, true);
diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
index cc2db6ed31..d2c7c75da8 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
@@ -28,8 +28,8 @@ namespace osu.Game.Screens.Select.Carousel
bool match =
criteria.Ruleset == null ||
- BeatmapInfo.RulesetID == criteria.Ruleset.ID ||
- (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
+ BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID ||
+ (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID > 0 && criteria.AllowConvertedBeatmaps);
if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true)
{
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1b2b3318bd..43bb0cdb88 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -37,7 +37,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 833e0fbdb9..49301fc329 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -61,7 +61,7 @@
-
+