diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d75f09f184..1019569b5b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -136,4 +136,4 @@ jobs:
run: dotnet workload install ios --from-rollback-file https://raw.githubusercontent.com/ppy/osu-framework/refs/heads/master/workloads.json
- name: Build
- run: dotnet build -c Debug osu.iOS
+ run: dotnet build -c Debug osu.iOS.slnf
diff --git a/osu.Android.props b/osu.Android.props
index b6ab7dc712..245d49abc2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -10,7 +10,7 @@
true
-
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+ iOS\%(RecursiveDir)%(Filename)%(Extension)
+
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 9747b654ae..916e1e757a 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -42,9 +42,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = Decoder.GetDecoder(stream);
var working = new TestWorkingBeatmap(decoder.Decode(stream));
- Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion);
- Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion);
- Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapInfo.BeatmapVersion);
+ Assert.AreEqual(6, working.Beatmap.BeatmapVersion);
+ Assert.That(working.Beatmap.BeatmapInfo.Ruleset.Name, Is.Not.EqualTo("null placeholder ruleset"));
+ Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapVersion);
}
}
@@ -59,9 +59,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
((LegacyBeatmapDecoder)decoder).ApplyOffsets = applyOffsets;
var working = new TestWorkingBeatmap(decoder.Decode(stream));
- Assert.AreEqual(4, working.BeatmapInfo.BeatmapVersion);
- Assert.AreEqual(4, working.Beatmap.BeatmapInfo.BeatmapVersion);
- Assert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapInfo.BeatmapVersion);
+ Assert.AreEqual(4, working.Beatmap.BeatmapVersion);
+ Assert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapVersion);
Assert.AreEqual(-1, working.BeatmapInfo.Metadata.PreviewTime);
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index 713f2f3fb1..de07e2be01 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -155,10 +155,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
var beatmap = new TestBeatmap(ruleset)
{
- BeatmapInfo =
- {
- BeatmapVersion = beatmapVersion
- }
+ BeatmapVersion = beatmapVersion
};
var score = new Score
@@ -633,14 +630,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
MD5Hash = md5Hash,
Ruleset = new OsuRuleset().RulesetInfo,
Difficulty = new BeatmapDifficulty(),
- BeatmapVersion = beatmapVersion,
},
- // needs to have at least one objects so that `StandardisedScoreMigrationTools` doesn't die
+ // needs to have at least one object so that `StandardisedScoreMigrationTools` doesn't die
// when trying to recompute total score.
HitObjects =
{
new HitCircle()
- }
+ },
+ BeatmapVersion = beatmapVersion,
});
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
index d1782da25f..2e7b55ab49 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
@@ -208,5 +208,11 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(7));
AddAssert("Correct beat divisor actually active", () => Editor.BeatDivisor, () => Is.EqualTo(7));
}
+
+ [Test]
+ public void TestBeatmapVersionPopulatedCorrectly()
+ {
+ AddAssert("beatmap version is populated", () => EditorBeatmap.BeatmapVersion > 0);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs
index 0d981014b8..396d2e9027 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs
@@ -50,30 +50,17 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestGameplay()
{
+ KiaiGameplayFountains fountains = null!;
+
AddStep("make fountains", () =>
{
Children = new[]
{
- new KiaiGameplayFountains.GameplayStarFountain
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- X = 75,
- },
- new KiaiGameplayFountains.GameplayStarFountain
- {
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- X = -75,
- },
+ fountains = new KiaiGameplayFountains(),
};
});
- AddStep("activate fountains", () =>
- {
- ((StarFountain)Children[0]).Shoot(1);
- ((StarFountain)Children[1]).Shoot(-1);
- });
+ AddStep("activate fountains", () => fountains.Shoot());
}
[Test]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
index fb9c801fb4..3e62417892 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
@@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
multiplayerRoom = new MultiplayerRoom(0)
{
- Playlist = { TestMultiplayerClient.CreateMultiplayerPlaylistItem(item) },
+ Playlist = { new MultiplayerPlaylistItem(item) },
Users = { localUser },
Host = localUser,
};
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index ec0117a990..ae939c7b9e 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -924,7 +924,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
enterGameplay();
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
- AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
+ AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
@@ -956,7 +956,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
enterGameplay();
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
- AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
+ AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
index 7c8691d5d1..1affa08813 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
@@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
///
private void addItemStep(bool expired = false, int? userId = null) => AddStep("add item", () =>
{
- MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
+ MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
{
Expired = expired,
PlayedAt = DateTimeOffset.Now
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
index 1a7b677798..7283e3a1fe 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
@@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add playlist item", () =>
{
- MultiplayerPlaylistItem item = TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
+ MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 325cb9e0cb..822e5f26bd 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -289,12 +289,37 @@ namespace osu.Game.Tests.Visual.Online
{
InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(0));
});
- AddAssert("guest mapper information not shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().All(s => s.Text != "BanchoBot"));
+ AddAssert("guest mapper information not shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().All(s => s.Text != "BanchoBot0"));
+ AddStep("move mouse to guest difficulty", () =>
+ {
+ InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1));
+ });
+ AddAssert("guest mapper information shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().Any(s => s.Text == "BanchoBot0"));
+ }
+
+ [Test]
+ public void TestBeatmapsetWithALotGuestOwner()
+ {
+ AddStep("show map with 2 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(2)));
+ AddStep("move mouse to guest difficulty", () =>
+ {
+ InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1));
+ });
+ AddStep("show map with 3 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(3)));
+ AddStep("move mouse to guest difficulty", () =>
+ {
+ InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1));
+ });
+ AddStep("show map with 10 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(10)));
+ AddStep("move mouse to guest difficulty", () =>
+ {
+ InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1));
+ });
+ AddStep("show map with 20 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(20)));
AddStep("move mouse to guest difficulty", () =>
{
InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1));
});
- AddAssert("guest mapper information shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().Any(s => s.Text == "BanchoBot"));
}
private APIBeatmapSet createManyDifficultiesBeatmapSet()
@@ -336,22 +361,31 @@ namespace osu.Game.Tests.Visual.Online
return beatmapSet;
}
- private APIBeatmapSet createBeatmapSetWithGuestDifficulty()
+ private APIBeatmapSet createBeatmapSetWithGuestDifficulty(int guestCount = 1)
{
var set = getBeatmapSet();
var beatmaps = new List();
+ var beatmapOwners = new List();
+ var ownersAPIUser = new List();
- var guestUser = new APIUser
+ for (int i = 0; i < guestCount; i++)
{
- Username = @"BanchoBot",
- Id = 3,
- };
+ var guestUser = new APIUser
+ {
+ Username = @$"BanchoBot{i}",
+ Id = i + 3,
+ };
- set.RelatedUsers = new[]
- {
- set.Author, guestUser
- };
+ beatmapOwners.Add(new APIBeatmap.BeatmapOwner
+ {
+ Username = @$"BanchoBot{i}",
+ Id = i + 3,
+ });
+ ownersAPIUser.Add(guestUser);
+ }
+
+ set.RelatedUsers = new[] { set.Author }.Concat(ownersAPIUser).ToArray();
beatmaps.Add(new APIBeatmap
{
@@ -366,7 +400,7 @@ namespace osu.Game.Tests.Visual.Online
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
},
- Status = BeatmapOnlineStatus.Graveyard
+ Status = BeatmapOnlineStatus.Graveyard,
});
beatmaps.Add(new APIBeatmap
@@ -382,7 +416,8 @@ namespace osu.Game.Tests.Visual.Online
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
},
- Status = BeatmapOnlineStatus.Graveyard
+ Status = BeatmapOnlineStatus.Graveyard,
+ BeatmapOwners = beatmapOwners.ToArray(),
});
set.Beatmaps = beatmaps.ToArray();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs
index 2e53ec2ba4..a1d0d40811 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs
@@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == 2);
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False);
- AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
+ AddStep("User began playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.True);
- AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
+ AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False);
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
@@ -88,13 +88,12 @@ namespace osu.Game.Tests.Visual.Online
{
IDisposable token = null!;
- AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
- AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
- AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == 2);
+ AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
+ AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == streamingUser.Id);
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.True);
- AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
+ AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False);
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
AddStep("End watching user presence", () => token.Dispose());
diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
index f7fd95a6e1..25611cf8d5 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
@@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Online
{
Username = "flyte",
Id = 3103765,
- IsOnline = true,
+ WasRecentlyOnline = true,
Statistics = new UserStatistics { GlobalRank = 1111 },
CountryCode = CountryCode.JP,
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
@@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Online
{
Username = "peppy",
Id = 2,
- IsOnline = false,
+ WasRecentlyOnline = false,
Statistics = new UserStatistics { GlobalRank = 2222 },
CountryCode = CountryCode.AU,
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
@@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Online
Id = 8195163,
CountryCode = CountryCode.BY,
CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
- IsOnline = false,
+ WasRecentlyOnline = false,
LastVisit = DateTimeOffset.Now
}
};
diff --git a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs
new file mode 100644
index 0000000000..17b437a051
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers.Markdown;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Testing;
+using osu.Game.Graphics.Containers.Markdown;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public partial class TestSceneImageProxying : OsuTestScene
+ {
+ [Test]
+ public void TestExternalImageLink()
+ {
+ MarkdownContainer markdown = null!;
+
+ // use base MarkdownContainer as a method of directly attempting to load an image without proxying logic.
+ AddStep("load external without proxying", () => Child = markdown = new MarkdownContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Text = "",
+ });
+ AddWaitStep("wait", 5);
+ AddAssert("image not loaded", () => markdown.ChildrenOfType().SingleOrDefault()?.Texture == null);
+
+ AddStep("load external with proxying", () => Child = markdown = new OsuMarkdownContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Text = "",
+ });
+ AddUntilStep("image loaded", () => markdown.ChildrenOfType().SingleOrDefault()?.Texture != null);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs
index fce888094d..29272f7336 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs
@@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Online
CountryCode = countryCode,
CoverUrl = cover,
Colour = color ?? "000000",
- IsOnline = true
+ WasRecentlyOnline = true
};
return new ClickableAvatar(user, showPanel)
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
index f4fc15da20..896bda364a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online
Id = 3103765,
CountryCode = CountryCode.JP,
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
- IsOnline = true
+ WasRecentlyOnline = true
}) { Width = 300 },
new UserGridPanel(new APIUser
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index 6167d1f760..193b356d71 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online
Id = 1001,
Username = "IAmOnline",
LastVisit = DateTimeOffset.Now,
- IsOnline = true,
+ WasRecentlyOnline = true,
}, new OsuRuleset().RulesetInfo));
AddStep("Show offline user", () => header.User.Value = new UserProfileData(new APIUser
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Online
Id = 1002,
Username = "IAmOffline",
LastVisit = DateTimeOffset.Now.AddDays(-10),
- IsOnline = false,
+ WasRecentlyOnline = false,
}, new OsuRuleset().RulesetInfo));
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs
index b406ea369f..f96d272e40 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Online;
using osu.Game.Scoring;
@@ -12,7 +13,7 @@ namespace osu.Game.Tests.Visual.Ranking
{
public partial class TestSceneOverallRanking : OsuTestScene
{
- private OverallRanking overallRanking = null!;
+ private readonly Bindable statisticsUpdate = new Bindable();
[Test]
public void TestUpdatePending()
@@ -104,14 +105,19 @@ namespace osu.Game.Tests.Visual.Ranking
displayUpdate(statistics, statistics);
}
- private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking
+ private void createDisplay() => AddStep("create display", () =>
{
- Width = 400,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre
+ statisticsUpdate.Value = null;
+ Child = new OverallRanking(new ScoreInfo())
+ {
+ Width = 400,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ DisplayedUpdate = { BindTarget = statisticsUpdate }
+ };
});
private void displayUpdate(UserStatistics before, UserStatistics after) =>
- AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
+ AddStep("display update", () => statisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
}
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
index b19288fd99..4758b70526 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
@@ -404,7 +404,7 @@ namespace osu.Game.Tests.Visual.Ranking
: base(score)
{
AllowRetry = true;
- ShowUserStatistics = true;
+ IsLocalPlay = true;
}
protected override void LoadComplete()
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs
index c12b9d29bc..df65023303 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs
@@ -11,6 +11,7 @@ using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
@@ -18,6 +19,9 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -36,6 +40,8 @@ namespace osu.Game.Tests.Visual.Ranking
{
public partial class TestSceneStatisticsPanel : OsuTestScene
{
+ private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
+
[Test]
public void TestScoreWithPositionStatistics()
{
@@ -137,62 +143,114 @@ namespace osu.Game.Tests.Visual.Ranking
{
CachedDependencies = [(typeof(UserStatisticsWatcher), userStatisticsWatcher)],
RelativeSizeAxes = Axes.Both,
- Child = new UserStatisticsPanel(score)
+ Child = new StatisticsPanel
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
- Score = { Value = score, }
+ Score = { Value = score, },
+ AchievedScore = score,
}
});
AddUntilStep("overall ranking present", () => this.ChildrenOfType().Any());
- AddUntilStep("loading spinner not visible", () => this.ChildrenOfType().All(l => l.State.Value == Visibility.Hidden));
+ AddUntilStep("loading spinner not visible",
+ () => this.ChildrenOfType().Single()
+ .ChildrenOfType().All(l => l.State.Value == Visibility.Hidden));
+ }
+
+ [Test]
+ public void TestTagging()
+ {
+ var score = TestResources.CreateTestScoreInfo();
+
+ AddStep("set up network requests", () =>
+ {
+ dummyAPI.HandleRequest = request =>
+ {
+ switch (request)
+ {
+ case ListTagsRequest listTagsRequest:
+ {
+ Scheduler.AddDelayed(() => listTagsRequest.TriggerSuccess(new APITagCollection
+ {
+ Tags =
+ [
+ new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", },
+ new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", },
+ new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", },
+ new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", },
+ ]
+ }), 500);
+ return true;
+ }
+
+ case GetBeatmapSetRequest getBeatmapSetRequest:
+ {
+ var beatmapSet = CreateAPIBeatmapSet(score.BeatmapInfo);
+ beatmapSet.Beatmaps.Single().TopTags =
+ [
+ new APIBeatmapTag { TagId = 3, VoteCount = 9 },
+ ];
+ Scheduler.AddDelayed(() => getBeatmapSetRequest.TriggerSuccess(beatmapSet), 500);
+ return true;
+ }
+
+ case AddBeatmapTagRequest:
+ case RemoveBeatmapTagRequest:
+ {
+ Scheduler.AddDelayed(request.TriggerSuccess, 500);
+ return true;
+ }
+ }
+
+ return false;
+ };
+ });
+ AddStep("load panel", () =>
+ {
+ Child = new PopoverContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new StatisticsPanel
+ {
+ RelativeSizeAxes = Axes.Both,
+ State = { Value = Visibility.Visible },
+ Score = { Value = score },
+ AchievedScore = score,
+ }
+ };
+ });
+ }
+
+ [Test]
+ public void TestTaggingWhenRankTooLow()
+ {
+ var score = TestResources.CreateTestScoreInfo();
+ score.Rank = ScoreRank.D;
+
+ AddStep("load panel", () =>
+ {
+ Child = new PopoverContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new StatisticsPanel
+ {
+ RelativeSizeAxes = Axes.Both,
+ State = { Value = Visibility.Visible },
+ Score = { Value = score },
+ AchievedScore = score,
+ }
+ };
+ });
}
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
{
- Child = new UserStatisticsPanel(score)
+ Child = new StatisticsPanel
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
Score = { Value = score },
- DisplayedUserStatisticsUpdate =
- {
- Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics
- {
- Level = new UserStatistics.LevelInfo
- {
- Current = 5,
- Progress = 20,
- },
- GlobalRank = 38000,
- CountryRank = 12006,
- PP = 2134,
- RankedScore = 21123849,
- Accuracy = 0.985,
- PlayCount = 13375,
- PlayTime = 354490,
- TotalScore = 128749597,
- TotalHits = 0,
- MaxCombo = 1233,
- }, new UserStatistics
- {
- Level = new UserStatistics.LevelInfo
- {
- Current = 5,
- Progress = 30,
- },
- GlobalRank = 36000,
- CountryRank = 12000,
- PP = (decimal)2134.5,
- RankedScore = 23897015,
- Accuracy = 0.984,
- PlayCount = 13376,
- PlayTime = 35789,
- TotalScore = 132218497,
- TotalHits = 0,
- MaxCombo = 1233,
- })
- }
+ AchievedScore = score,
};
});
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs
index ebfd553815..d622df8d76 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Ranking
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
- Child = new UserTagControl
+ Child = new UserTagControl(Beatmap.Value.BeatmapInfo)
{
Width = 500,
Anchor = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index 6eb9263c7e..499b28fb49 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -993,7 +993,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("column not scrolled", () => modSelectOverlay.ChildrenOfType().Single().IsScrolledToStart());
AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero));
- AddAssert("customisation panel closed",
+ AddUntilStep("customisation panel closed",
() => this.ChildrenOfType().Single().ExpandedState.Value,
() => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
@@ -1018,7 +1018,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private void assertCustomisationToggleState(bool disabled, bool active)
{
AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().Enabled.Value == !disabled);
- AddAssert($"customisation panel is {(active ? "" : "not ")}active",
+ AddUntilStep($"customisation panel is {(active ? "" : "not ")}active",
() => modSelectOverlay.ChildrenOfType().Single().ExpandedState.Value,
() => active ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
}
diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs
index 1d710e6395..d3ab86a8a0 100644
--- a/osu.Game/Audio/PreviewTrackManager.cs
+++ b/osu.Game/Audio/PreviewTrackManager.cs
@@ -6,9 +6,9 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
+using osu.Game.Online;
namespace osu.Game.Audio
{
@@ -30,7 +30,7 @@ namespace osu.Game.Audio
[BackgroundDependencyLoader]
private void load(AudioManager audioManager)
{
- trackStore = audioManager.GetTrackStore(new OnlineStore());
+ trackStore = audioManager.GetTrackStore(new TrustedDomainOnlineStore());
}
///
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 8ea6fa1f51..155ded5747 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -9,6 +9,7 @@ using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
using Newtonsoft.Json;
using osu.Framework.Lists;
+using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Serialization.Converters;
namespace osu.Game.Beatmaps
@@ -141,6 +142,8 @@ namespace osu.Game.Beatmaps
public int[] Bookmarks { get; set; } = Array.Empty();
+ public int BeatmapVersion { get; set; } = LegacyBeatmapEncoder.FIRST_LAZER_VERSION;
+
IBeatmap IBeatmap.Clone() => Clone();
public Beatmap Clone() => (Beatmap)MemberwiseClone();
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 0cf10c659b..f0cb6d0484 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -86,6 +86,7 @@ namespace osu.Game.Beatmaps
beatmap.Countdown = original.Countdown;
beatmap.CountdownOffset = original.CountdownOffset;
beatmap.Bookmarks = original.Bookmarks;
+ beatmap.BeatmapVersion = original.BeatmapVersion;
return beatmap;
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 333ec89eab..a6b40a26de 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -125,9 +125,10 @@ namespace osu.Game.Beatmaps
///
/// Reset any fetched online linking information (and history).
///
- public void ResetOnlineInfo()
+ public void ResetOnlineInfo(bool resetOnlineId = true)
{
- OnlineID = -1;
+ if (resetOnlineId)
+ OnlineID = -1;
LastOnlineUpdate = null;
OnlineMD5Hash = string.Empty;
if (Status != BeatmapOnlineStatus.LocallyModified)
@@ -231,8 +232,6 @@ namespace osu.Game.Beatmaps
[Obsolete("Use ScoreManager.GetMaximumAchievableComboAsync instead.")]
public int? MaxCombo { get; set; }
- public int BeatmapVersion;
-
public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone();
public override string ToString() => this.GetDisplayTitle();
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index b0aabe3787..765f2be345 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps.Formats
protected override void ParseStreamInto(LineBufferedReader stream, Beatmap beatmap)
{
this.beatmap = beatmap;
- this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion;
+ this.beatmap.BeatmapVersion = FormatVersion;
parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion);
ApplyLegacyDefaults(this.beatmap);
@@ -193,6 +193,10 @@ namespace osu.Game.Beatmaps.Formats
internal static void ApplyLegacyDefaults(Beatmap beatmap)
{
beatmap.WidescreenStoryboard = false;
+ // in a perfect world this would throw if osu! ruleset couldn't be found,
+ // but unfortunately there are "legitimate" cases where it's not there (i.e. ruleset test projects),
+ // so attempt to trudge on with whatever it is that's in `BeatmapInfo` if the lookup fails.
+ beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(0) ?? beatmap.BeatmapInfo.Ruleset;
}
protected override void ParseLine(Beatmap beatmap, Section section, string line)
diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs
index f95fcefd7e..482bc73742 100644
--- a/osu.Game/Beatmaps/IBeatmap.cs
+++ b/osu.Game/Beatmaps/IBeatmap.cs
@@ -109,6 +109,8 @@ namespace osu.Game.Beatmaps
int[] Bookmarks { get; internal set; }
+ int BeatmapVersion { get; }
+
///
/// Creates a shallow-clone of this beatmap and returns it.
///
diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs
index 3212e17b7b..7142f2b300 100644
--- a/osu.Game/Database/RealmAccess.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -97,8 +97,9 @@ namespace osu.Game.Database
/// 45 2024-12-23 Change beat snap divisor adjust defaults to be Ctrl+Scroll instead of Ctrl+Shift+Scroll, if not already changed by user.
/// 46 2024-12-26 Change beat snap divisor bindings to match stable directionality ¯\_(ツ)_/¯.
/// 47 2025-01-21 Remove right mouse button binding for absolute scroll. Never use mouse buttons (or scroll) for global actions.
+ /// 48 2025-03-19 Clear online status for all qualified beatmaps (some were stuck in a qualified state due to local caching issues).
///
- private const int schema_version = 47;
+ private const int schema_version = 48;
///
/// Lock object which is held during sections, blocking realm retrieval during blocking periods.
@@ -1245,6 +1246,15 @@ namespace osu.Game.Database
break;
}
+
+ case 48:
+ const int qualified = (int)BeatmapOnlineStatus.Qualified;
+
+ var beatmaps = migration.NewRealm.All().Where(b => b.StatusInt == qualified);
+
+ foreach (var beatmap in beatmaps)
+ beatmap.ResetOnlineInfo(resetOnlineId: false);
+ break;
}
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs
index 10207dd389..ff7df18f00 100644
--- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs
+++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs
@@ -17,5 +17,8 @@ namespace osu.Game.Graphics.Containers.Markdown
{
TooltipText = linkInline.Title;
}
+
+ protected override ImageContainer CreateImageContainer(string url)
+ => base.CreateImageContainer($@"https://osu.ppy.sh/media-url?url={url}");
}
}
diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs
index d5cce1a10a..8da8b7ed7d 100644
--- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers
private partial class ArbitraryDrawableWrapper : Container, IHasLineBaseHeight
{
- public float LineBaseHeight => DrawHeight;
+ public float LineBaseHeight => (Child as IHasLineBaseHeight)?.LineBaseHeight ?? DrawHeight;
public ArbitraryDrawableWrapper()
{
diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
index b3ffd15816..e5a4e807b5 100644
--- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
+++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Graphics.UserInterface
{
if (Link == null) return;
- game?.CopyUrlToClipboard(Link);
+ game?.CopyToClipboard(Link);
}
}
}
diff --git a/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs
index d3e8c0f8c8..22f9fe6d02 100644
--- a/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs
+++ b/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs
@@ -17,7 +17,13 @@ namespace osu.Game.Localisation.SkinComponents
///
/// "Whether to show extended information for each mod."
///
- public static LocalisableString ShowExtendedInformationDescription => new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod.");
+ public static LocalisableString ShowExtendedInformationDescription =>
+ new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod.");
+
+ ///
+ /// "Display direction"
+ ///
+ public static LocalisableString DisplayDirection => new TranslatableString(getKey(@"display_direction"), "Display direction");
///
/// "Expansion mode"
diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs
index 49e8d00371..b520511d8f 100644
--- a/osu.Game/Localisation/ToastStrings.cs
+++ b/osu.Game/Localisation/ToastStrings.cs
@@ -45,9 +45,9 @@ namespace osu.Game.Localisation
public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved");
///
- /// "Link copied to clipboard"
+ /// "Copied to clipboard"
///
- public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"Link copied to clipboard");
+ public static LocalisableString CopiedToClipboard => new TranslatableString(getKey(@"copied_to_clipboard"), @"Copied to clipboard");
///
/// "Speed changed to {0:N2}x"
diff --git a/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs b/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs
index 4fa02dc569..911c4fa5f1 100644
--- a/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs
+++ b/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Globalization;
using System.Net.Http;
using osu.Framework.IO.Network;
@@ -21,11 +20,10 @@ namespace osu.Game.Online.API.Requests
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
- req.Method = HttpMethod.Post;
- req.AddParameter(@"tag_id", TagID.ToString(CultureInfo.InvariantCulture), RequestParameterType.Query);
+ req.Method = HttpMethod.Put;
return req;
}
- protected override string Target => $@"beatmaps/{BeatmapID}/tags";
+ protected override string Target => $@"beatmaps/{BeatmapID}/tags/{TagID}";
}
}
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
index 66e17739a8..055d2dd8e2 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
@@ -109,6 +109,9 @@ namespace osu.Game.Online.API.Requests.Responses
public double BPM { get; set; }
+ [JsonProperty(@"owners")]
+ public BeatmapOwner[] BeatmapOwners { get; set; } = Array.Empty();
+
#region Implementation of IBeatmapInfo
public IBeatmapMetadataInfo Metadata => (BeatmapSet as IBeatmapSetInfo)?.Metadata ?? new BeatmapMetadata();
@@ -177,5 +180,14 @@ namespace osu.Game.Online.API.Requests.Responses
// ReSharper disable once NonReadonlyMemberInGetHashCode
public override int GetHashCode() => OnlineID;
}
+
+ public class BeatmapOwner
+ {
+ [JsonProperty(@"id")]
+ public int Id { get; set; }
+
+ [JsonProperty(@"username")]
+ public string Username { get; set; } = string.Empty;
+ }
}
}
diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs
index 92b7d9d874..4e219cdf22 100644
--- a/osu.Game/Online/API/Requests/Responses/APIUser.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs
@@ -9,6 +9,7 @@ using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Game.Extensions;
+using osu.Game.Online.Metadata;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses
@@ -111,8 +112,13 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"is_active")]
public bool Active;
+ ///
+ /// From osu-web's perspective, whether a user was recently online.
+ /// This doesn't imply the user is online in a lazer client (may be updated from stable or web browser).
+ /// Use for real-time lazer online status checks.
+ ///
[JsonProperty(@"is_online")]
- public bool IsOnline;
+ public bool WasRecentlyOnline;
[JsonProperty(@"pm_friends_only")]
public bool PMFriendsOnly;
diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs
index 9885419b65..0679191a52 100644
--- a/osu.Game/Online/Metadata/MetadataClient.cs
+++ b/osu.Game/Online/Metadata/MetadataClient.cs
@@ -57,6 +57,9 @@ namespace osu.Game.Online.Metadata
///
/// Attempts to retrieve the presence of a user.
///
+ ///
+ /// This will return data if the client is currently receiving presence data. See .
+ ///
/// The user ID.
/// The user presence, or null if not available or the user's offline.
public UserPresence? GetPresence(int userId)
diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs
index 91f009b76f..76e5cb0404 100644
--- a/osu.Game/Online/Spectator/SpectatorClient.cs
+++ b/osu.Game/Online/Spectator/SpectatorClient.cs
@@ -47,11 +47,6 @@ namespace osu.Game.Online.Spectator
///
public IBindableList WatchingUsers => watchingUsers;
- ///
- /// A global list of all players currently playing.
- ///
- public IBindableList PlayingUsers => playingUsers;
-
///
/// Whether the local user is playing.
///
@@ -91,7 +86,6 @@ namespace osu.Game.Online.Spectator
private readonly BindableDictionary watchedUserStates = new BindableDictionary();
private readonly BindableList watchingUsers = new BindableList();
- private readonly BindableList playingUsers = new BindableList();
private readonly SpectatorState currentState = new SpectatorState();
private IBeatmap? currentBeatmap;
@@ -134,7 +128,6 @@ namespace osu.Game.Online.Spectator
}
else
{
- playingUsers.Clear();
watchedUserStates.Clear();
watchingUsers.Clear();
}
@@ -145,9 +138,6 @@ namespace osu.Game.Online.Spectator
{
Schedule(() =>
{
- if (!playingUsers.Contains(userId))
- playingUsers.Add(userId);
-
if (watchedUsersRefCounts.ContainsKey(userId))
watchedUserStates[userId] = state;
@@ -161,8 +151,6 @@ namespace osu.Game.Online.Spectator
{
Schedule(() =>
{
- playingUsers.Remove(userId);
-
if (watchedUsersRefCounts.ContainsKey(userId))
watchedUserStates[userId] = state;
diff --git a/osu.Game/Online/TrustedDomainOnlineStore.cs b/osu.Game/Online/TrustedDomainOnlineStore.cs
new file mode 100644
index 0000000000..2b47f159e6
--- /dev/null
+++ b/osu.Game/Online/TrustedDomainOnlineStore.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.IO.Stores;
+using osu.Framework.Logging;
+
+namespace osu.Game.Online
+{
+ public sealed class TrustedDomainOnlineStore : OnlineStore
+ {
+ protected override string GetLookupUrl(string url)
+ {
+ if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Log($@"Blocking resource lookup from external website: {url}", LoggingTarget.Network, LogLevel.Important);
+ return string.Empty;
+ }
+
+ return url;
+ }
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 4a9154f14b..3381553970 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -519,10 +519,10 @@ namespace osu.Game
}
});
- public void CopyUrlToClipboard(string url) => waitForReady(() => onScreenDisplay, _ =>
+ public void CopyToClipboard(string value) => waitForReady(() => onScreenDisplay, _ =>
{
- dependencies.Get().SetText(url);
- onScreenDisplay.Display(new CopyUrlToast());
+ dependencies.Get().SetText(value);
+ onScreenDisplay.Display(new CopiedToClipboardToast());
});
public void OpenUrlExternally(string url, LinkWarnMode warnMode = LinkWarnMode.Default) => waitForReady(() => externalLinkOpener, _ => externalLinkOpener.OpenUrlExternally(url, warnMode));
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 7d35207bbe..4087a8b71e 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -108,6 +108,8 @@ namespace osu.Game
public virtual EndpointConfiguration CreateEndpoints() =>
UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
+ protected override OnlineStore CreateOnlineStore() => new TrustedDomainOnlineStore();
+
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
///
@@ -278,7 +280,7 @@ namespace osu.Game
dependencies.CacheAs(Storage);
var largeStore = new LargeTextureStore(Host.Renderer, Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")));
- largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore()));
+ largeStore.AddTextureSource(Host.CreateTextureLoaderStore(CreateOnlineStore()));
dependencies.Cache(largeStore);
dependencies.CacheAs(LocalConfig);
diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
index a7838651a9..eea0b087eb 100644
--- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
+++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Extensions;
@@ -31,9 +32,7 @@ namespace osu.Game.Overlays.BeatmapSet
private const float tile_icon_padding = 7;
private const float tile_spacing = 2;
- private readonly OsuSpriteText version, starRating, starRatingText;
- private readonly LinkFlowContainer guestMapperContainer;
- private readonly FillFlowContainer starRatingContainer;
+ private readonly LinkFlowContainer infoContainer;
private readonly Statistic plays, favourites;
public readonly DifficultiesContainer Difficulties;
@@ -53,6 +52,9 @@ namespace osu.Game.Overlays.BeatmapSet
}
}
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
+
public BeatmapPicker()
{
RelativeSizeAxes = Axes.X;
@@ -72,59 +74,13 @@ namespace osu.Game.Overlays.BeatmapSet
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2), Bottom = 10 },
- OnLostHover = () =>
- {
- showBeatmap(Beatmap.Value);
- starRatingContainer.FadeOut(100);
- },
+ OnLostHover = () => showBeatmap(Beatmap.Value, withStarRating: false),
},
- new FillFlowContainer
+ infoContainer = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 11))
{
- AutoSizeAxes = Axes.Both,
- Spacing = new Vector2(5f),
- Children = new Drawable[]
- {
- version = new OsuSpriteText
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold)
- },
- guestMapperContainer = new LinkFlowContainer(s =>
- s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 11))
- {
- AutoSizeAxes = Axes.Both,
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Margin = new MarginPadding { Bottom = 1 },
- },
- starRatingContainer = new FillFlowContainer
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Alpha = 0,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(2f, 0),
- Margin = new MarginPadding { Bottom = 1 },
- Children = new[]
- {
- starRatingText = new OsuSpriteText
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold),
- Text = BeatmapsetsStrings.ShowStatsStars,
- },
- starRating = new OsuSpriteText
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold),
- Text = string.Empty,
- },
- }
- },
- },
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ TextAnchor = Anchor.BottomLeft,
},
new FillFlowContainer
{
@@ -144,7 +100,7 @@ namespace osu.Game.Overlays.BeatmapSet
Beatmap.ValueChanged += b =>
{
- showBeatmap(b.NewValue);
+ showBeatmap(b.NewValue, withStarRating: Difficulties.Any(d => d.IsHovered));
updateDifficultyButtons();
};
}
@@ -153,10 +109,8 @@ namespace osu.Game.Overlays.BeatmapSet
private IBindable ruleset { get; set; } = null!;
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
- starRating.Colour = colours.Yellow;
- starRatingText.Colour = colours.Yellow;
updateDisplay();
}
@@ -185,16 +139,12 @@ namespace osu.Game.Overlays.BeatmapSet
State = DifficultySelectorState.NotSelected,
OnHovered = beatmap =>
{
- showBeatmap(beatmap);
- starRating.Text = beatmap.StarRating.FormatStarRating();
- starRatingContainer.FadeIn(100);
+ showBeatmap(beatmap, withStarRating: true);
},
OnClicked = beatmap => { Beatmap.Value = beatmap; },
});
}
- starRatingContainer.FadeOut(100);
-
// If a selection is already made, try and maintain it.
if (Beatmap.Value != null)
Beatmap.Value = Difficulties.FirstOrDefault(b => b.Beatmap.OnlineID == Beatmap.Value.OnlineID)?.Beatmap;
@@ -208,22 +158,68 @@ namespace osu.Game.Overlays.BeatmapSet
updateDifficultyButtons();
}
- private void showBeatmap(APIBeatmap? beatmapInfo)
+ private void showBeatmap(APIBeatmap? beatmapInfo, bool withStarRating)
{
- guestMapperContainer.Clear();
+ infoContainer.Clear();
- if (beatmapInfo?.AuthorID != BeatmapSet?.AuthorID)
+ infoContainer.AddText(beatmapInfo?.DifficultyName ?? string.Empty, s => s.Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold));
+ infoContainer.AddArbitraryDrawable(Empty().With(e => e.Width = 5));
+
+ var beatmapOwners = beatmapInfo?.BeatmapOwners;
+ bool isHostDifficulty = beatmapOwners?.Length == 1 && beatmapOwners.First().Id == beatmapSet?.AuthorID;
+
+ if (beatmapOwners != null && !isHostDifficulty)
{
- APIUser? user = BeatmapSet?.RelatedUsers?.SingleOrDefault(u => u.OnlineID == beatmapInfo?.AuthorID);
+ APIUser[] users = BeatmapSet?.RelatedUsers?.Where(u => beatmapOwners.Any(o => o.Id == u.OnlineID)).ToArray() ?? [];
+ int count = users.Length;
- if (user != null)
+ switch (count)
{
- guestMapperContainer.AddText("mapped by ");
- guestMapperContainer.AddUserLink(user);
+ case 0:
+ break;
+
+ case 1:
+ infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty));
+ infoContainer.AddUserLink(users[0]);
+ break;
+
+ case 2:
+ infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty));
+ infoContainer.AddUserLink(users[0]);
+ infoContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector);
+ infoContainer.AddUserLink(users[1]);
+ break;
+
+ default:
+ {
+ infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty));
+
+ for (int i = 0; i < count; i++)
+ {
+ infoContainer.AddUserLink(users[i]);
+
+ if (i < count - 2)
+ infoContainer.AddText(CommonStrings.ArrayAndWordsConnector);
+ else if (i == count - 2)
+ infoContainer.AddText(CommonStrings.ArrayAndLastWordConnector);
+ }
+
+ break;
+ }
}
}
- version.Text = beatmapInfo?.DifficultyName ?? string.Empty;
+ if (withStarRating)
+ {
+ infoContainer.AddArbitraryDrawable(Empty().With(e => e.Width = 5));
+ infoContainer.AddText(
+ LocalisableString.Interpolate($"{BeatmapsetsStrings.ShowStatsStars} {beatmapInfo?.StarRating.FormatStarRating()}"),
+ t =>
+ {
+ t.Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold);
+ t.Colour = colours.Yellow;
+ });
+ }
}
private void updateDifficultyButtons()
diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
index a50043f0f0..c72c2a6698 100644
--- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
+++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
@@ -98,7 +98,7 @@ namespace osu.Game.Overlays.BeatmapSet
{
Vertical = BeatmapSetOverlay.Y_PADDING,
Left = WaveOverlayContainer.HORIZONTAL_PADDING,
- Right = WaveOverlayContainer.HORIZONTAL_PADDING + BeatmapSetOverlay.RIGHT_WIDTH,
+ Right = WaveOverlayContainer.HORIZONTAL_PADDING + BeatmapSetOverlay.RIGHT_WIDTH + 10,
},
Children = new Drawable[]
{
diff --git a/osu.Game/Overlays/Chat/DrawableChatUsername.cs b/osu.Game/Overlays/Chat/DrawableChatUsername.cs
index 83f67d1a8a..57338dde9f 100644
--- a/osu.Game/Overlays/Chat/DrawableChatUsername.cs
+++ b/osu.Game/Overlays/Chat/DrawableChatUsername.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@@ -14,6 +15,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
+using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -22,7 +24,10 @@ using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat;
+using osu.Game.Online.Multiplayer;
using osu.Game.Resources.Localisation.Web;
+using osu.Game.Screens;
+using osu.Game.Screens.Play;
using osuTK;
using osuTK.Graphics;
using ChatStrings = osu.Game.Localisation.ChatStrings;
@@ -69,6 +74,12 @@ namespace osu.Game.Overlays.Chat
[Resolved]
private OsuColour colours { get; set; } = null!;
+ [Resolved]
+ private MultiplayerClient? multiplayerClient { get; set; }
+
+ [Resolved]
+ private IPerformFromScreenRunner? performer { get; set; }
+
[Resolved(canBeNull: true)]
private ChannelManager? chatManager { get; set; }
@@ -161,13 +172,10 @@ namespace osu.Game.Overlays.Chat
if (user.Equals(APIUser.SYSTEM_USER))
return Array.Empty
/// The score to create the rows for.
/// The beatmap on which the score was set.
- protected virtual ICollection CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap)
- => newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap);
+ protected virtual IEnumerable CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap)
+ {
+ foreach (var statistic in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap))
+ yield return statistic;
+
+ if (AchievedScore != null
+ && newScore.UserID > 1
+ && newScore.UserID == AchievedScore.UserID
+ && newScore.OnlineID > 0
+ && newScore.OnlineID == AchievedScore.OnlineID)
+ {
+ yield return new StatisticItem("Overall Ranking", () => new OverallRanking(newScore)
+ {
+ RelativeSizeAxes = Axes.X,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+ }
+
+ if (AchievedScore != null
+ && newScore.BeatmapInfo!.OnlineID > 0
+ && api.IsLoggedIn)
+ {
+ if (
+ // We may want to iterate on this condition
+ AchievedScore.Rank >= ScoreRank.C
+ )
+ {
+ yield return new StatisticItem("Tag the beatmap!", () => new UserTagControl(newScore.BeatmapInfo)
+ {
+ RelativeSizeAxes = Axes.X,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+ }
+ else
+ {
+ yield return new StatisticItem("Tag the beatmap!", () => new OsuTextFlowContainer(cp => cp.Font = OsuFont.GetFont(size: StatisticItem.FONT_SIZE, weight: FontWeight.SemiBold))
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ TextAnchor = Anchor.Centre,
+ Text = "Set a better score to contribute to beatmap tags!",
+ });
+ }
+ }
+ }
protected override bool OnClick(ClickEvent e)
{
diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs
index 9f5afea6f0..9d0a511f5a 100644
--- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs
+++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs
@@ -5,8 +5,10 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
+using osu.Game.Scoring;
namespace osu.Game.Screens.Ranking.Statistics.User
{
@@ -14,13 +16,21 @@ namespace osu.Game.Screens.Ranking.Statistics.User
{
private const float transition_duration = 300;
- public Bindable StatisticsUpdate { get; } = new Bindable();
+ public Bindable DisplayedUpdate { get; } = new Bindable();
+ private readonly IBindable latestGlobalStatisticsUpdate = new Bindable();
+
+ private readonly ScoreInfo scoreInfo;
private LoadingLayer loadingLayer = null!;
private GridContainer content = null!;
+ public OverallRanking(ScoreInfo scoreInfo)
+ {
+ this.scoreInfo = scoreInfo;
+ }
+
[BackgroundDependencyLoader]
- private void load()
+ private void load(UserStatisticsWatcher? userStatisticsWatcher)
{
AutoSizeAxes = Axes.Y;
AutoSizeEasing = Easing.OutQuint;
@@ -55,34 +65,44 @@ namespace osu.Game.Screens.Ranking.Statistics.User
{
new Drawable[]
{
- new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
+ new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
new SimpleStatisticTable.Spacer(),
- new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
+ new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
},
[],
new Drawable[]
{
- new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
+ new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
new SimpleStatisticTable.Spacer(),
- new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
+ new AccuracyChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
},
[],
new Drawable[]
{
- new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
+ new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
new SimpleStatisticTable.Spacer(),
- new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
+ new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
}
}
}
};
+
+ if (userStatisticsWatcher != null)
+ {
+ latestGlobalStatisticsUpdate.BindTo(userStatisticsWatcher.LatestUpdate);
+ latestGlobalStatisticsUpdate.BindValueChanged(update =>
+ {
+ if (update.NewValue?.Score.MatchesOnlineID(scoreInfo) == true)
+ DisplayedUpdate.Value = update.NewValue;
+ }, true);
+ }
}
protected override void LoadComplete()
{
base.LoadComplete();
- StatisticsUpdate.BindValueChanged(onUpdateReceived, true);
+ DisplayedUpdate.BindValueChanged(onUpdateReceived, true);
FinishTransforms(true);
}
diff --git a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs
deleted file mode 100644
index 86fed4a9bb..0000000000
--- a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
-using osu.Game.Extensions;
-using osu.Game.Online;
-using osu.Game.Scoring;
-using osu.Game.Screens.Ranking.Statistics.User;
-
-namespace osu.Game.Screens.Ranking.Statistics
-{
- public partial class UserStatisticsPanel : StatisticsPanel
- {
- private readonly ScoreInfo achievedScore;
-
- internal readonly Bindable DisplayedUserStatisticsUpdate = new Bindable();
-
- private IBindable latestGlobalStatisticsUpdate = null!;
-
- public UserStatisticsPanel(ScoreInfo achievedScore)
- {
- this.achievedScore = achievedScore;
- }
-
- [BackgroundDependencyLoader]
- private void load(UserStatisticsWatcher? userStatisticsWatcher)
- {
- if (userStatisticsWatcher != null)
- {
- latestGlobalStatisticsUpdate = userStatisticsWatcher.LatestUpdate.GetBoundCopy();
- latestGlobalStatisticsUpdate.BindValueChanged(update =>
- {
- if (update.NewValue?.Score.MatchesOnlineID(achievedScore) == true)
- DisplayedUserStatisticsUpdate.Value = update.NewValue;
- }, true);
- }
- }
-
- protected override ICollection CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap)
- {
- var items = base.CreateStatisticItems(newScore, playableBeatmap);
-
- if (newScore.UserID > 1
- && newScore.UserID == achievedScore.UserID
- && newScore.OnlineID > 0
- && newScore.OnlineID == achievedScore.OnlineID)
- {
- items = items.Append(new StatisticItem("Overall Ranking", () => new OverallRanking
- {
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- StatisticsUpdate = { BindTarget = DisplayedUserStatisticsUpdate }
- })).ToArray();
- }
-
- return items;
- }
- }
-}
diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs
index 7600d0aaae..ae4a918ae5 100644
--- a/osu.Game/Screens/Ranking/UserTagControl.cs
+++ b/osu.Game/Screens/Ranking/UserTagControl.cs
@@ -27,9 +27,11 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays;
using osuTK;
using osuTK.Input;
@@ -37,6 +39,8 @@ namespace osu.Game.Screens.Ranking
{
public partial class UserTagControl : CompositeDrawable
{
+ private readonly BeatmapInfo beatmapInfo;
+
public override bool HandlePositionalInput => true;
private readonly Cached layout = new Cached();
@@ -53,8 +57,10 @@ namespace osu.Game.Screens.Ranking
[Resolved]
private IAPIProvider api { get; set; } = null!;
- [Resolved]
- private Bindable beatmap { get; set; } = null!;
+ public UserTagControl(BeatmapInfo beatmapInfo)
+ {
+ this.beatmapInfo = beatmapInfo;
+ }
[BackgroundDependencyLoader]
private void load(SessionStatics sessionStatics)
@@ -104,8 +110,8 @@ namespace osu.Game.Screens.Ranking
api.Queue(listTagsRequest);
}
- var getBeatmapSetRequest = new GetBeatmapSetRequest(beatmap.Value.BeatmapInfo.BeatmapSet!.OnlineID);
- getBeatmapSetRequest.Success += set => apiBeatmap.Value = set.Beatmaps.SingleOrDefault(b => b.MatchesOnlineID(beatmap.Value.BeatmapInfo));
+ var getBeatmapSetRequest = new GetBeatmapSetRequest(beatmapInfo.BeatmapSet!.OnlineID);
+ getBeatmapSetRequest.Success += set => apiBeatmap.Value = set.Beatmaps.SingleOrDefault(b => b.MatchesOnlineID(beatmapInfo));
api.Queue(getBeatmapSetRequest);
}
@@ -114,7 +120,7 @@ namespace osu.Game.Screens.Ranking
loadingLayer.Show();
extraTags.Remove(tag);
- var req = new AddBeatmapTagRequest(beatmap.Value.BeatmapInfo.OnlineID, tag.Id);
+ var req = new AddBeatmapTagRequest(beatmapInfo.OnlineID, tag.Id);
req.Success += () =>
{
tag.Voted.Value = true;
@@ -495,21 +501,45 @@ namespace osu.Game.Screens.Ranking
searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true);
}
+ public override bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (base.OnPressed(e))
+ return true;
+
+ if (e.Repeat)
+ return false;
+
+ if (State.Value == Visibility.Hidden)
+ return false;
+
+ if (e.Action == GlobalAction.Select)
+ {
+ attemptSelect();
+ return true;
+ }
+
+ return false;
+ }
+
protected override bool OnKeyDown(KeyDownEvent e)
{
- var visibleItems = searchContainer.OfType().Where(d => d.IsPresent).ToArray();
-
if (e.Key == Key.Enter)
{
- if (visibleItems.Length == 1)
- select(visibleItems.Single().Tag);
-
+ attemptSelect();
return true;
}
return base.OnKeyDown(e);
}
+ private void attemptSelect()
+ {
+ var visibleItems = searchContainer.OfType().Where(d => d.IsPresent).ToArray();
+
+ if (visibleItems.Length == 1)
+ select(visibleItems.Single().Tag);
+ }
+
private void select(UserTag tag)
{
OnSelected?.Invoke(tag);
@@ -530,14 +560,14 @@ namespace osu.Game.Screens.Ranking
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load(OsuColour colours, OverlayColourProvider? colourProvider)
{
Content.AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = colours.GreySeaFoamDark,
+ Colour = colourProvider?.Background3 ?? colours.GreySeaFoamDark,
Depth = float.MaxValue,
},
new FillFlowContainer
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
index 4451cfcf32..b99f046f4b 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
@@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel
items.Add(new OsuMenuItem("Collections") { Items = collectionItems });
if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url)
- items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url)));
+ items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyToClipboard(url)));
if (manager != null)
items.Add(new OsuMenuItem("Mark as played", MenuItemType.Standard, () => manager.MarkPlayed(beatmapInfo)));
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
index 996d9ea0ab..c410cb7d69 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
@@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet)));
if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url)
- items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url)));
+ items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyToClipboard(url)));
if (dialogOverlay != null)
items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet))));
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index cc9a82c1ba..febd7f54ff 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -238,7 +238,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = ServerAPIRoom.QueueMode,
AutoStartDuration = ServerAPIRoom.AutoStartDuration
},
- Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(),
+ Playlist = ServerAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(),
Users = { localUser },
Host = localUser
};
@@ -687,21 +687,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
return MessagePackSerializer.Deserialize(serialized, SignalRUnionWorkaroundResolver.OPTIONS);
}
- public static MultiplayerPlaylistItem CreateMultiplayerPlaylistItem(PlaylistItem item) => new MultiplayerPlaylistItem
- {
- ID = item.ID,
- OwnerID = item.OwnerID,
- BeatmapID = item.Beatmap.OnlineID,
- BeatmapChecksum = item.Beatmap.MD5Hash,
- RulesetID = item.RulesetID,
- RequiredMods = item.RequiredMods.ToArray(),
- AllowedMods = item.AllowedMods.ToArray(),
- Expired = item.Expired,
- PlaylistOrder = item.PlaylistOrder ?? 0,
- PlayedAt = item.PlayedAt,
- StarRating = item.Beatmap.StarRating,
- };
-
public override Task DisconnectInternal()
{
isConnected.Value = false;
diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs
index b6fa4bbac6..0185165b36 100644
--- a/osu.Game/Users/ExtendedUserPanel.cs
+++ b/osu.Game/Users/ExtendedUserPanel.cs
@@ -90,6 +90,7 @@ namespace osu.Game.Users
private void updatePresence()
{
+ // TODO: we probably don't want to do this every frame.
UserPresence? presence = metadata?.GetPresence(User.OnlineID);
UserStatus status = presence?.Status ?? UserStatus.Offline;
UserActivity? activity = presence?.Activity;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 5b5482b3c7..af052ae93b 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -35,8 +35,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 486979487b..260b0cc0c3 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -17,6 +17,6 @@
-all
-
+