mirror of
https://github.com/ppy/osu
synced 2025-02-18 11:26:57 +00:00
Remove beatmap/ruleset/etc from MultiplayerRoomSettings
This commit is contained in:
parent
a1c9b56083
commit
db87e42d47
@ -106,5 +106,17 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// Signals that the match has ended, all players have finished and results are ready to be displayed.
|
||||
/// </summary>
|
||||
Task ResultsReady();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that an item has been added to the playlist.
|
||||
/// </summary>
|
||||
/// <param name="item">The added item.</param>
|
||||
Task PlaylistItemAdded(APIPlaylistItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that an item has been removed from the playlist.
|
||||
/// </summary>
|
||||
/// <param name="item">The removed item.</param>
|
||||
Task PlaylistItemRemoved(APIPlaylistItem item);
|
||||
}
|
||||
}
|
||||
|
@ -76,5 +76,17 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
|
||||
/// <exception cref="InvalidStateException">If an attempt to start the game occurs when the game's (or users') state disallows it.</exception>
|
||||
Task StartMatch();
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the playlist.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
Task AddPlaylistItem(APIPlaylistItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the playlist.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
Task RemovePlaylistItem(APIPlaylistItem item);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using MessagePack;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osu.Game.Online.Multiplayer.Queueing;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
@ -14,7 +13,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Serializable]
|
||||
[MessagePackObject]
|
||||
[Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||
[Union(1, typeof(EnqueuePlaylistItemRequest))]
|
||||
public abstract class MatchUserRequest
|
||||
{
|
||||
}
|
||||
|
@ -301,6 +301,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public abstract Task StartMatch();
|
||||
|
||||
public abstract Task AddPlaylistItem(APIPlaylistItem item);
|
||||
|
||||
public abstract Task RemovePlaylistItem(APIPlaylistItem item);
|
||||
|
||||
Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state)
|
||||
{
|
||||
if (Room == null)
|
||||
@ -416,11 +420,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings)
|
||||
{
|
||||
updateLocalRoomSettings(newSettings);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) => updateLocalRoomSettings(newSettings);
|
||||
|
||||
Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state)
|
||||
{
|
||||
@ -572,6 +572,59 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlaylistItemAdded(APIPlaylistItem item)
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
var set = await GetOnlineBeatmapSet(item.BeatmapID).ConfigureAwait(false);
|
||||
|
||||
var beatmap = set.Beatmaps.Single(b => b.OnlineBeatmapID == item.BeatmapID);
|
||||
beatmap.MD5Hash = item.BeatmapChecksum;
|
||||
|
||||
var ruleset = Rulesets.GetRuleset(item.RulesetID);
|
||||
|
||||
await scheduleAsync(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
var playlistItem = new PlaylistItem
|
||||
{
|
||||
ID = item.ID,
|
||||
Beatmap = { Value = beatmap },
|
||||
Ruleset = { Value = ruleset },
|
||||
};
|
||||
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
|
||||
playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance)));
|
||||
|
||||
APIRoom.Playlist.Add(playlistItem);
|
||||
RoomUpdated?.Invoke();
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task PlaylistItemRemoved(APIPlaylistItem item)
|
||||
{
|
||||
if (Room == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return scheduleAsync(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
APIRoom.Playlist.RemoveAll(i => i.ID == item.ID);
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the <see cref="User"/> for a given <see cref="MultiplayerRoomUser"/>.
|
||||
/// </summary>
|
||||
@ -597,62 +650,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room.Settings = settings;
|
||||
APIRoom.Name.Value = Room.Settings.Name;
|
||||
APIRoom.Password.Value = Room.Settings.Password;
|
||||
|
||||
// The current item update is delayed until an online beatmap lookup (below) succeeds.
|
||||
// In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here.
|
||||
CurrentMatchPlayingItem.Value = null;
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
|
||||
GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(set => Schedule(() =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
updatePlaylist(settings, set.Result);
|
||||
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId);
|
||||
}, cancellationToken);
|
||||
|
||||
private void updatePlaylist(MultiplayerRoomSettings settings, BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
if (Room == null || !Room.Settings.Equals(settings))
|
||||
return;
|
||||
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID);
|
||||
beatmap.MD5Hash = settings.BeatmapChecksum;
|
||||
|
||||
var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance();
|
||||
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
|
||||
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
|
||||
|
||||
// Try to retrieve the existing playlist item from the API room.
|
||||
var playlistItem = APIRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId);
|
||||
|
||||
if (playlistItem != null)
|
||||
updateItem(playlistItem);
|
||||
else
|
||||
{
|
||||
// An existing playlist item does not exist, so append a new one.
|
||||
updateItem(playlistItem = new PlaylistItem());
|
||||
APIRoom.Playlist.Add(playlistItem);
|
||||
}
|
||||
|
||||
CurrentMatchPlayingItem.Value = playlistItem;
|
||||
|
||||
void updateItem(PlaylistItem item)
|
||||
{
|
||||
item.ID = settings.PlaylistItemId;
|
||||
item.Beatmap.Value = beatmap;
|
||||
item.Ruleset.Value = ruleset.RulesetInfo;
|
||||
item.RequiredMods.Clear();
|
||||
item.RequiredMods.AddRange(mods);
|
||||
item.AllowedMods.Clear();
|
||||
item.AllowedMods.AddRange(allowedMods);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a <see cref="BeatmapSetInfo"/> from an online source.
|
||||
/// </summary>
|
||||
|
@ -4,10 +4,7 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MessagePack;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer.Queueing;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
@ -18,53 +15,29 @@ namespace osu.Game.Online.Multiplayer
|
||||
public class MultiplayerRoomSettings : IEquatable<MultiplayerRoomSettings>
|
||||
{
|
||||
[Key(0)]
|
||||
public int BeatmapID { get; set; }
|
||||
|
||||
[Key(1)]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[Key(2)]
|
||||
public string BeatmapChecksum { get; set; } = string.Empty;
|
||||
|
||||
[Key(3)]
|
||||
public string Name { get; set; } = "Unnamed room";
|
||||
|
||||
[Key(4)]
|
||||
public IEnumerable<APIMod> RequiredMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(5)]
|
||||
public IEnumerable<APIMod> AllowedMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(6)]
|
||||
[Key(1)]
|
||||
public long PlaylistItemId { get; set; }
|
||||
|
||||
[Key(7)]
|
||||
[Key(2)]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
[Key(8)]
|
||||
[Key(3)]
|
||||
public MatchType MatchType { get; set; } = MatchType.HeadToHead;
|
||||
|
||||
[Key(9)]
|
||||
[Key(4)]
|
||||
public QueueModes QueueMode { get; set; } = QueueModes.HostOnly;
|
||||
|
||||
public bool Equals(MultiplayerRoomSettings other)
|
||||
=> BeatmapID == other.BeatmapID
|
||||
&& BeatmapChecksum == other.BeatmapChecksum
|
||||
&& RequiredMods.SequenceEqual(other.RequiredMods)
|
||||
&& AllowedMods.SequenceEqual(other.AllowedMods)
|
||||
&& RulesetID == other.RulesetID
|
||||
&& Password.Equals(other.Password, StringComparison.Ordinal)
|
||||
=> Password.Equals(other.Password, StringComparison.Ordinal)
|
||||
&& Name.Equals(other.Name, StringComparison.Ordinal)
|
||||
&& PlaylistItemId == other.PlaylistItemId
|
||||
&& MatchType == other.MatchType
|
||||
&& QueueMode == other.QueueMode;
|
||||
|
||||
public override string ToString() => $"Name:{Name}"
|
||||
+ $" Beatmap:{BeatmapID} ({BeatmapChecksum})"
|
||||
+ $" RequiredMods:{string.Join(',', RequiredMods)}"
|
||||
+ $" AllowedMods:{string.Join(',', AllowedMods)}"
|
||||
+ $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}"
|
||||
+ $" Ruleset:{RulesetID}"
|
||||
+ $" Type:{MatchType}"
|
||||
+ $" Item:{PlaylistItemId}"
|
||||
+ $" Queue:{QueueMode}";
|
||||
|
@ -62,6 +62,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On<MatchRoomState>(nameof(IMultiplayerClient.MatchRoomStateChanged), ((IMultiplayerClient)this).MatchRoomStateChanged);
|
||||
connection.On<int, MatchUserState>(nameof(IMultiplayerClient.MatchUserStateChanged), ((IMultiplayerClient)this).MatchUserStateChanged);
|
||||
connection.On<MatchServerEvent>(nameof(IMultiplayerClient.MatchEvent), ((IMultiplayerClient)this).MatchEvent);
|
||||
connection.On<APIPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded);
|
||||
connection.On<APIPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved);
|
||||
};
|
||||
|
||||
IsConnected.BindTo(connector.IsConnected);
|
||||
@ -148,6 +150,22 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch));
|
||||
}
|
||||
|
||||
public override Task AddPlaylistItem(APIPlaylistItem item)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item);
|
||||
}
|
||||
|
||||
public override Task RemovePlaylistItem(APIPlaylistItem item)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), item);
|
||||
}
|
||||
|
||||
protected override Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<BeatmapSetInfo>();
|
||||
|
@ -8,23 +8,40 @@ using System.Linq;
|
||||
using MessagePack;
|
||||
using osu.Game.Online.API;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer.Queueing
|
||||
namespace osu.Game.Online.Rooms
|
||||
{
|
||||
public class EnqueuePlaylistItemRequest : MatchUserRequest
|
||||
public class APIPlaylistItem
|
||||
{
|
||||
[Key(0)]
|
||||
public int BeatmapID { get; set; }
|
||||
public long ID { get; set; }
|
||||
|
||||
[Key(1)]
|
||||
public int RulesetID { get; set; }
|
||||
public int BeatmapID { get; set; }
|
||||
|
||||
[Key(2)]
|
||||
public string BeatmapChecksum { get; set; } = string.Empty;
|
||||
|
||||
[Key(3)]
|
||||
public IEnumerable<APIMod> RequiredMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[Key(4)]
|
||||
public IEnumerable<APIMod> RequiredMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(5)]
|
||||
public IEnumerable<APIMod> AllowedMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
public APIPlaylistItem()
|
||||
{
|
||||
}
|
||||
|
||||
public APIPlaylistItem(PlaylistItem item)
|
||||
{
|
||||
ID = item.ID;
|
||||
BeatmapID = item.BeatmapID;
|
||||
BeatmapChecksum = item.Beatmap.Value?.MD5Hash ?? string.Empty;
|
||||
RulesetID = item.RulesetID;
|
||||
RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray();
|
||||
AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ using MessagePack.Formatters;
|
||||
using MessagePack.Resolvers;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osu.Game.Online.Multiplayer.Queueing;
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
@ -26,7 +25,6 @@ namespace osu.Game.Online
|
||||
{ typeof(TeamVersusUserState), new TypeRedirectingFormatter<TeamVersusUserState, MatchUserState>() },
|
||||
{ typeof(TeamVersusRoomState), new TypeRedirectingFormatter<TeamVersusRoomState, MatchRoomState>() },
|
||||
{ typeof(ChangeTeamRequest), new TypeRedirectingFormatter<ChangeTeamRequest, MatchUserRequest>() },
|
||||
{ typeof(EnqueuePlaylistItemRequest), new TypeRedirectingFormatter<EnqueuePlaylistItemRequest, MatchUserRequest>() },
|
||||
|
||||
// These should not be required. The fallback should work. But something is weird with the way caching is done.
|
||||
// For future adventurers, I would not advise looking into this further. It's likely not worth the effort.
|
||||
|
@ -9,7 +9,6 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.Queueing;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -57,11 +56,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
loadingLayer.Show();
|
||||
|
||||
client.SendMatchRequest(new EnqueuePlaylistItemRequest
|
||||
client.AddPlaylistItem(new APIPlaylistItem
|
||||
{
|
||||
BeatmapID = item.BeatmapID,
|
||||
RulesetID = item.RulesetID,
|
||||
BeatmapChecksum = item.Beatmap.Value.MD5Hash,
|
||||
RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(),
|
||||
AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray()
|
||||
}).ContinueWith(t =>
|
||||
|
@ -184,7 +184,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
|
||||
const double fade_time = 50;
|
||||
|
||||
var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance();
|
||||
// Todo: FIX THIS
|
||||
var ruleset = rulesets.GetRuleset(0).CreateInstance();
|
||||
|
||||
var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank;
|
||||
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osu.Game.Online.Multiplayer.Queueing;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Users;
|
||||
@ -41,6 +42,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private readonly TestRequestHandlingMultiplayerRoomManager roomManager;
|
||||
|
||||
private long lastPlaylistItemId;
|
||||
|
||||
public TestMultiplayerClient(TestRequestHandlingMultiplayerRoomManager roomManager)
|
||||
{
|
||||
this.roomManager = roomManager;
|
||||
@ -141,6 +144,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
if (password != apiRoom.Password.Value)
|
||||
throw new InvalidOperationException("Invalid password.");
|
||||
|
||||
lastPlaylistItemId = apiRoom.Playlist.Last().ID;
|
||||
|
||||
var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id)
|
||||
{
|
||||
User = api.LocalUser.Value
|
||||
@ -152,12 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Name = apiRoom.Name.Value,
|
||||
MatchType = apiRoom.Type.Value,
|
||||
BeatmapID = apiRoom.Playlist.Last().BeatmapID,
|
||||
RulesetID = apiRoom.Playlist.Last().RulesetID,
|
||||
BeatmapChecksum = apiRoom.Playlist.Last().Beatmap.Value.MD5Hash,
|
||||
RequiredMods = apiRoom.Playlist.Last().RequiredMods.Select(m => new APIMod(m)).ToArray(),
|
||||
AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(),
|
||||
PlaylistItemId = apiRoom.Playlist.Last().ID,
|
||||
PlaylistItemId = lastPlaylistItemId,
|
||||
// ReSharper disable once ConstantNullCoalescingCondition Incorrect inspection due to lack of nullable in Room.cs.
|
||||
Password = password ?? string.Empty,
|
||||
},
|
||||
@ -265,6 +265,49 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return ((IMultiplayerClient)this).LoadRequested();
|
||||
}
|
||||
|
||||
public override async Task AddPlaylistItem(APIPlaylistItem item)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
item.ID = lastPlaylistItemId + 1;
|
||||
|
||||
switch (Room.Settings.QueueMode)
|
||||
{
|
||||
case QueueModes.HostOnly:
|
||||
if (Room.Host?.UserID != LocalUser?.UserID)
|
||||
throw new InvalidOperationException("Local user is not the room host.");
|
||||
|
||||
await RemovePlaylistItem(new APIPlaylistItem(APIRoom.Playlist.Single(i => i.ID == lastPlaylistItemId))).ConfigureAwait(false);
|
||||
await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false);
|
||||
|
||||
Room.Settings.PlaylistItemId = item.ID;
|
||||
await ChangeSettings(Room.Settings).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case QueueModes.FreeForAll:
|
||||
await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false);
|
||||
// Todo: Advance if the added item is the last non-expired one.
|
||||
break;
|
||||
|
||||
case QueueModes.FairRotate:
|
||||
// Todo: Not implemented.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
lastPlaylistItemId = item.ID;
|
||||
}
|
||||
|
||||
public override Task RemovePlaylistItem(APIPlaylistItem item)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
if (Room.Host?.UserID != LocalUser?.UserID)
|
||||
throw new InvalidOperationException("Local user is not the room host.");
|
||||
|
||||
return ((IMultiplayerClient)this).PlaylistItemRemoved(item);
|
||||
}
|
||||
|
||||
protected override Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
|
Loading…
Reference in New Issue
Block a user