mirror of
https://github.com/ppy/osu
synced 2024-12-15 03:16:17 +00:00
Merge pull request #11808 from smoogipoo/multiplayer-no-playlist-mangling
Rework multiplayer playlist handling to support multiple items
This commit is contained in:
commit
a2aec6bcdc
@ -36,18 +36,23 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Key(5)]
|
||||
public IEnumerable<APIMod> AllowedMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(6)]
|
||||
public long PlaylistItemId { get; set; }
|
||||
|
||||
public bool Equals(MultiplayerRoomSettings other)
|
||||
=> BeatmapID == other.BeatmapID
|
||||
&& BeatmapChecksum == other.BeatmapChecksum
|
||||
&& RequiredMods.SequenceEqual(other.RequiredMods)
|
||||
&& AllowedMods.SequenceEqual(other.AllowedMods)
|
||||
&& RulesetID == other.RulesetID
|
||||
&& Name.Equals(other.Name, StringComparison.Ordinal);
|
||||
&& Name.Equals(other.Name, StringComparison.Ordinal)
|
||||
&& PlaylistItemId == other.PlaylistItemId;
|
||||
|
||||
public override string ToString() => $"Name:{Name}"
|
||||
+ $" Beatmap:{BeatmapID} ({BeatmapChecksum})"
|
||||
+ $" RequiredMods:{string.Join(',', RequiredMods)}"
|
||||
+ $" AllowedMods:{string.Join(',', AllowedMods)}"
|
||||
+ $" Ruleset:{RulesetID}";
|
||||
+ $" Ruleset:{RulesetID}"
|
||||
+ $" Item:{PlaylistItemId}";
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
public readonly BindableList<int> CurrentMatchPlayingUserIds = new BindableList<int>();
|
||||
|
||||
public readonly Bindable<PlaylistItem?> CurrentMatchPlayingItem = new Bindable<PlaylistItem?>();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available.
|
||||
/// </summary>
|
||||
@ -92,10 +94,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
private Room? apiRoom;
|
||||
// Only exists for compatibility with old osu-server-spectator build.
|
||||
// Todo: Can be removed on 2021/02/26.
|
||||
private long defaultPlaylistItemId;
|
||||
|
||||
// Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise.
|
||||
private long playlistItemId;
|
||||
private Room? apiRoom;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -142,7 +145,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
Room = joinedRoom;
|
||||
apiRoom = room;
|
||||
playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0;
|
||||
defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0;
|
||||
}, cancellationSource.Token);
|
||||
|
||||
// Update room settings.
|
||||
@ -218,7 +221,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
|
||||
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
|
||||
RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods,
|
||||
AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods
|
||||
AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods,
|
||||
});
|
||||
}
|
||||
|
||||
@ -506,14 +509,13 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room.Settings = settings;
|
||||
apiRoom.Name.Value = Room.Settings.Name;
|
||||
|
||||
// The playlist update is delayed until an online beatmap lookup (below) succeeds.
|
||||
// In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here.
|
||||
apiRoom.Playlist.Clear();
|
||||
// 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();
|
||||
|
||||
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
|
||||
|
||||
req.Success += res =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
@ -540,18 +542,30 @@ namespace osu.Game.Online.Multiplayer
|
||||
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
|
||||
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
|
||||
|
||||
PlaylistItem playlistItem = new PlaylistItem
|
||||
// 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
|
||||
{
|
||||
ID = playlistItemId,
|
||||
Beatmap = { Value = beatmap },
|
||||
Ruleset = { Value = ruleset.RulesetInfo },
|
||||
};
|
||||
// An existing playlist item does not exist, so append a new one.
|
||||
updateItem(playlistItem = new PlaylistItem());
|
||||
apiRoom.Playlist.Add(playlistItem);
|
||||
}
|
||||
|
||||
playlistItem.RequiredMods.AddRange(mods);
|
||||
playlistItem.AllowedMods.AddRange(allowedMods);
|
||||
CurrentMatchPlayingItem.Value = playlistItem;
|
||||
|
||||
apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
|
||||
apiRoom.Playlist.Add(playlistItem);
|
||||
void updateItem(PlaylistItem item)
|
||||
{
|
||||
item.ID = settings.PlaylistItemId == 0 ? defaultPlaylistItemId : 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>
|
||||
|
@ -23,6 +23,12 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonProperty("ruleset_id")]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="PlaylistItem"/> is still a valid selection for the <see cref="Room"/>.
|
||||
/// </summary>
|
||||
[JsonProperty("expired")]
|
||||
public bool Expired { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
|
||||
|
||||
|
@ -153,6 +153,12 @@ namespace osu.Game.Online.Rooms
|
||||
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
|
||||
Status.Value = new RoomStatusEnded();
|
||||
|
||||
// Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended,
|
||||
// and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room.
|
||||
// More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room.
|
||||
if (!(Status.Value is RoomStatusEnded))
|
||||
other.Playlist.RemoveAll(i => i.Expired);
|
||||
|
||||
if (!Playlist.SequenceEqual(other.Playlist))
|
||||
{
|
||||
Playlist.Clear();
|
||||
|
@ -21,15 +21,13 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
[Cached(typeof(IPreviewTrackOwner))]
|
||||
public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner
|
||||
{
|
||||
[Cached(typeof(IBindable<PlaylistItem>))]
|
||||
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
private Sample sampleStart;
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any mods applied by/to the local user.
|
||||
/// </summary>
|
||||
@ -73,7 +71,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
|
||||
SelectedItem.Value = Playlist.FirstOrDefault();
|
||||
|
||||
managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
|
||||
managerUpdated.BindValueChanged(beatmapUpdated);
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -13,7 +11,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public class BeatmapSelectionControl : OnlinePlayComposite
|
||||
public class BeatmapSelectionControl : RoomSubScreenComposite
|
||||
{
|
||||
[Resolved]
|
||||
private MultiplayerMatchSubScreen matchSubScreen { get; set; }
|
||||
@ -60,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged(onPlaylistChanged, true);
|
||||
SelectedItem.BindValueChanged(_ => updateBeatmap(), true);
|
||||
Host.BindValueChanged(host =>
|
||||
{
|
||||
if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true)
|
||||
@ -70,12 +68,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
private void updateBeatmap()
|
||||
{
|
||||
if (Playlist.Any())
|
||||
beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(Playlist.Single(), false, false);
|
||||
else
|
||||
if (SelectedItem.Value == null)
|
||||
beatmapPanelContainer.Clear();
|
||||
else
|
||||
beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(SelectedItem.Value, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
@ -47,7 +46,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
||||
private Drawable userModsSection;
|
||||
|
||||
private IBindable<bool> isConnected;
|
||||
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable readyClickOperation;
|
||||
@ -269,14 +268,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged(onPlaylistChanged, true);
|
||||
SelectedItem.BindTo(client.CurrentMatchPlayingItem);
|
||||
SelectedItem.BindValueChanged(onSelectedItemChanged, true);
|
||||
|
||||
BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true);
|
||||
UserMods.BindValueChanged(onUserModsChanged);
|
||||
|
||||
client.LoadRequested += onLoadRequested;
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
|
||||
isConnected = client.IsConnected.GetBoundCopy();
|
||||
isConnected.BindTo(client.IsConnected);
|
||||
isConnected.BindValueChanged(connected =>
|
||||
{
|
||||
if (!connected.NewValue)
|
||||
@ -284,6 +285,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void onSelectedItemChanged(ValueChangedEvent<PlaylistItem> item)
|
||||
{
|
||||
if (client?.LocalUser == null)
|
||||
return;
|
||||
|
||||
if (item.NewValue?.AllowedMods.Any() != true)
|
||||
{
|
||||
userModsSection.Hide();
|
||||
userModsSelectOverlay.Hide();
|
||||
userModsSelectOverlay.IsValidMod = _ => false;
|
||||
}
|
||||
else
|
||||
{
|
||||
userModsSection.Show();
|
||||
userModsSelectOverlay.IsValidMod = m => item.NewValue.AllowedMods.Any(a => a.GetType() == m.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateMods()
|
||||
{
|
||||
if (SelectedItem.Value == null || client.LocalUser == null)
|
||||
@ -312,23 +331,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return base.OnBackButton();
|
||||
}
|
||||
|
||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
SelectedItem.Value = Playlist.FirstOrDefault();
|
||||
|
||||
if (SelectedItem.Value?.AllowedMods.Any() != true)
|
||||
{
|
||||
userModsSection.Hide();
|
||||
userModsSelectOverlay.Hide();
|
||||
userModsSelectOverlay.IsValidMod = _ => false;
|
||||
}
|
||||
else
|
||||
{
|
||||
userModsSection.Show();
|
||||
userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
private ModSettingChangeTracker modSettingChangeTracker;
|
||||
private ScheduledDelegate debouncedModSettingsUpdate;
|
||||
|
||||
|
@ -2,14 +2,19 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="CompositeDrawable"/> that exposes bindables for <see cref="Room"/> properties.
|
||||
/// </summary>
|
||||
public class OnlinePlayComposite : CompositeDrawable
|
||||
{
|
||||
[Resolved(typeof(Room))]
|
||||
@ -53,5 +58,23 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<TimeSpan?> Duration { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected item in the <see cref="RoomSubScreen"/>, or the first item from <see cref="Playlist"/>
|
||||
/// if this <see cref="OnlinePlayComposite"/> is not within a <see cref="RoomSubScreen"/>.
|
||||
/// </summary>
|
||||
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true);
|
||||
}
|
||||
|
||||
protected virtual void UpdateSelectedItem()
|
||||
{
|
||||
SelectedItem.Value = Playlist.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -31,6 +32,10 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
|
||||
[CanBeNull]
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IBindable<PlaylistItem> selectedItem { get; set; }
|
||||
|
||||
private readonly Bindable<IReadOnlyList<Mod>> freeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
||||
|
||||
@ -66,8 +71,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
// At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods.
|
||||
// Similarly, freeMods is currently empty but should only contain the allowed mods.
|
||||
Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||
freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||
Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||
freeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||
|
||||
Ruleset.BindValueChanged(onRulesetChanged);
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
[Resolved(typeof(Room), nameof(Room.RoomID))]
|
||||
private Bindable<long?> roomId { get; set; }
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||
private BindableList<PlaylistItem> playlist { get; set; }
|
||||
|
||||
private MatchSettingsOverlay settingsOverlay;
|
||||
private MatchLeaderboard leaderboard;
|
||||
|
||||
@ -117,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
new DrawableRoomPlaylistWithResults
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Playlist },
|
||||
Items = { BindTarget = playlist },
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
RequestShowResults = item =>
|
||||
{
|
||||
@ -222,7 +225,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
|
||||
// Set the first playlist item.
|
||||
// This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()).
|
||||
Schedule(() => SelectedItem.Value = Playlist.FirstOrDefault());
|
||||
Schedule(() => SelectedItem.Value = playlist.FirstOrDefault());
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
38
osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs
Normal file
38
osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="OnlinePlayComposite"/> with additional logic tracking the currently-selected <see cref="PlaylistItem"/> inside a <see cref="RoomSubScreen"/>.
|
||||
/// </summary>
|
||||
public class RoomSubScreenComposite : OnlinePlayComposite
|
||||
{
|
||||
[Resolved]
|
||||
private IBindable<PlaylistItem> subScreenSelectedItem { get; set; }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
subScreenSelectedItem.BindValueChanged(_ => UpdateSelectedItem(), true);
|
||||
}
|
||||
|
||||
protected override void UpdateSelectedItem()
|
||||
{
|
||||
if (RoomID.Value == null)
|
||||
{
|
||||
// If the room hasn't been created yet, fall-back to the base logic.
|
||||
base.UpdateSelectedItem();
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedItem.Value = subScreenSelectedItem.Value;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user