Fix potentially incorrect online play beatmap availability#36121
Merged
peppy merged 3 commits intoppy:masterfrom Dec 24, 2025
Merged
Fix potentially incorrect online play beatmap availability#36121peppy merged 3 commits intoppy:masterfrom
peppy merged 3 commits intoppy:masterfrom
Conversation
56f79ed to
ecaf3e0
Compare
Member
|
It's getting quite a struggle to track all these locally copy-pasted cases. How about something like this? diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 62c03b4fcd..d50862a369 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -328,6 +328,14 @@ public List<BeatmapSetInfo> GetAllUsableBeatmapSets()
.Filter(query, arguments)
.FirstOrDefault()?.Detach());
+ /// <summary>
+ /// Perform a lookup query on available <see cref="BeatmapInfo"/>s for a specific online ID.
+ /// </summary>
+ /// <returns>A matching local beatmap info if existing and in a valid state.</returns>
+ public BeatmapInfo? QueryOnlineBeatmapId(int id) => Realm.Run(r =>
+ r.All<BeatmapInfo>()
+ .ForOnlineId(id).SingleOrDefault()?.Detach());
+
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs
index 1bb6b0aba4..65ae42a3da 100644
--- a/osu.Game/Database/RealmExtensions.cs
+++ b/osu.Game/Database/RealmExtensions.cs
@@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Logging;
+using osu.Game.Beatmaps;
using Realms;
namespace osu.Game.Database
@@ -102,5 +104,13 @@ public static T Write<T>(this Realm realm, Func<Realm, T> function)
/// Quite often we only care about changes at a collection level. This can be used to guard and early-return when no such changes are in a callback.
/// </remarks>
public static bool HasCollectionChanges(this ChangeSet changes) => changes.InsertedIndices.Length > 0 || changes.DeletedIndices.Length > 0 || changes.Moves.Length > 0;
+
+ public static IQueryable<BeatmapInfo> NotDeleted(this IQueryable<BeatmapInfo> beatmaps) =>
+ beatmaps.Filter($@"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false");
+
+ public static IQueryable<BeatmapInfo> ForOnlineId(this IQueryable<BeatmapInfo> beatmaps, int id) =>
+ beatmaps
+ .NotDeleted()
+ .Filter($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}", id);
}
}
diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs
index c3648a7edf..6db293ec71 100644
--- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs
+++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs
@@ -489,7 +489,7 @@ public static void TrySetDailyChallengeBeatmap(OsuScreen screen, BeatmapManager
if (!screen.IsCurrentScreen())
return;
- var beatmap = beatmaps.QueryBeatmap($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}", item.Beatmap.OnlineID);
+ var beatmap = beatmaps.QueryOnlineBeatmapId(item.Beatmap.OnlineID);
screen.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally.
screen.Ruleset.Value = rulesets.GetRuleset(item.RulesetID);
diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.cs
index a689ac85ee..d692783e48 100644
--- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.cs
+++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.cs
@@ -246,7 +246,7 @@ private void updateGameplayState()
// Update global gameplay state to correspond to the new selection.
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
- var localBeatmap = beatmapManager.QueryBeatmap($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}", item.BeatmapID);
+ var localBeatmap = beatmapManager.QueryOnlineBeatmapId(item.BeatmapID);
if (localBeatmap != null)
{
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 16c6a46a9c..05fd5b8c69 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -510,7 +510,7 @@ private void onActivePlaylistItemChanged()
{
MultiplayerPlaylistItem item = client.Room.CurrentPlaylistItem;
- var newBeatmap = beatmapManager.QueryBeatmap($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}", item.BeatmapID);
+ var newBeatmap = beatmapManager.QueryOnlineBeatmapId(item.BeatmapID);
if (!Beatmap.Value.BeatmapSetInfo.Equals(newBeatmap?.BeatmapSet))
this.MakeCurrent();
@@ -652,7 +652,7 @@ private void updateGameplayState()
// Update global gameplay state to correspond to the new selection.
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
- var localBeatmap = beatmapManager.QueryBeatmap($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}", gameplayBeatmapId);
+ var localBeatmap = beatmapManager.QueryOnlineBeatmapId(gameplayBeatmapId);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
Ruleset.Value = ruleset;
Mods.Value = client.LocalUser.Mods.Concat(item.RequiredMods).Select(m => m.ToMod(rulesetInstance)).ToArray();
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs
index d4d161a3d0..1bb67cc3af 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayBeatmapAvailabilityTracker.cs
@@ -17,7 +17,6 @@
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
-using Realms;
namespace osu.Game.Screens.OnlinePlay
{
@@ -153,13 +152,9 @@ void updateAvailability()
}
}
- IQueryable<BeatmapInfo> queryBeatmap()
- {
- // See: BeatmapManager.QueryBeatmap()
- return realm.Realm.All<BeatmapInfo>()
- .Filter($@"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false")
- .Filter($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}", beatmap.OnlineID);
- }
+ IQueryable<BeatmapInfo> queryBeatmap() =>
+ realm.Realm.All<BeatmapInfo>()
+ .ForOnlineId(beatmap.OnlineID);
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs
index e994299606..df5a9db8e7 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs
@@ -62,8 +62,7 @@ protected PlaylistItemResultsScreen(ScoreInfo? score, long roomId, PlaylistItem
[BackgroundDependencyLoader]
private void load()
{
- var localBeatmap = beatmapManager.QueryBeatmap($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}",
- PlaylistItem.Beatmap.OnlineID);
+ var localBeatmap = beatmapManager.QueryOnlineBeatmapId(PlaylistItem.Beatmap.OnlineID);
itemBeatmap = beatmapManager.GetWorkingBeatmap(localBeatmap);
AddInternal(new Container
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
index fdda6f6c85..58e260c857 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
@@ -609,7 +609,7 @@ private void updateGameplayState()
// Update global gameplay state to correspond to the new selection.
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
- var localBeatmap = beatmapManager.QueryBeatmap($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}", gameplayBeatmap.OnlineID);
+ var localBeatmap = beatmapManager.QueryOnlineBeatmapId(gameplayBeatmap.OnlineID);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
Ruleset.Value = gameplayRuleset;
Mods.Value = UserMods.Value.Concat(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToArray();
|
Co-authored-by: Dean Herbert <pe@ppy.sh>
peppy
approved these changes
Dec 24, 2025
This was referenced Dec 24, 2025
This was referenced Jan 1, 2026
This was referenced Jan 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Has been discussed several times already. The condition added in this PR is the same as used in multiplayer, playlists, DC, and QP.
I believe this to be one of the contributors to #36045. Some background knowledge is necessary:
LoadRequestedto all users in the room regardless of their beatmap availability. This is an issue on its own that will be fixed separately.LoadRequested, blocks gameplay from being entered if not in the correct (WaitingForLoadorSpectating) state.Readystate and beatmap availability to the server BUT only relies on the ready state to decide when a match can be started.LoadRequestedto all users, but only keep track of those that have the beatmap available for gameplay purposes.tl;dr: It's all messed up. Iteratively returning to sanity.