mirror of
https://github.com/ppy/osu.git
synced 2024-12-05 09:42:54 +08:00
Merge pull request #30525 from smoogipoo/multiplayer-refactor
Replace `MultiplayerRoomComposite` with local bindings
This commit is contained in:
commit
54288c350a
@ -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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
@ -36,15 +34,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private readonly Bindable<BeatmapAvailability> beatmapAvailability = new Bindable<BeatmapAvailability>();
|
||||
private readonly Bindable<Room> room = new Bindable<Room>();
|
||||
|
||||
private MultiplayerRoom multiplayerRoom;
|
||||
private MultiplayerRoomUser localUser;
|
||||
private OngoingOperationTracker ongoingOperationTracker;
|
||||
private MultiplayerRoom multiplayerRoom = null!;
|
||||
private MultiplayerRoomUser localUser = null!;
|
||||
private OngoingOperationTracker ongoingOperationTracker = null!;
|
||||
|
||||
private PopoverContainer content;
|
||||
private MatchStartControl control;
|
||||
private PopoverContainer content = null!;
|
||||
private MatchStartControl control = null!;
|
||||
|
||||
private OsuButton readyButton => control.ChildrenOfType<OsuButton>().Single();
|
||||
|
||||
[Cached(typeof(IBindable<PlaylistItem>))]
|
||||
private readonly Bindable<PlaylistItem> currentItem = new Bindable<PlaylistItem>();
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent)) { Model = { BindTarget = room } };
|
||||
|
||||
@ -112,15 +113,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
beatmapAvailability.Value = BeatmapAvailability.LocallyAvailable();
|
||||
|
||||
var playlistItem = new PlaylistItem(Beatmap.Value.BeatmapInfo)
|
||||
currentItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
|
||||
{
|
||||
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID
|
||||
};
|
||||
|
||||
room.Value = new Room
|
||||
{
|
||||
Playlist = { playlistItem },
|
||||
CurrentPlaylistItem = { Value = playlistItem }
|
||||
Playlist = { currentItem.Value },
|
||||
CurrentPlaylistItem = { BindTarget = currentItem }
|
||||
};
|
||||
|
||||
localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id) { User = API.LocalUser.Value };
|
||||
@ -129,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Playlist =
|
||||
{
|
||||
TestMultiplayerClient.CreateMultiplayerPlaylistItem(playlistItem),
|
||||
TestMultiplayerClient.CreateMultiplayerPlaylistItem(currentItem.Value),
|
||||
},
|
||||
Users = { localUser },
|
||||
Host = localUser,
|
||||
|
@ -1,15 +1,21 @@
|
||||
// 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.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneMultiplayerMatchFooter : MultiplayerTestScene
|
||||
{
|
||||
[Cached(typeof(IBindable<PlaylistItem>))]
|
||||
private readonly Bindable<PlaylistItem> currentItem = new Bindable<PlaylistItem>();
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
@ -1,13 +1,12 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
@ -29,10 +28,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneMultiplayerPlaylist : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerPlaylist list;
|
||||
private BeatmapManager beatmaps;
|
||||
private BeatmapSetInfo importedSet;
|
||||
private BeatmapInfo importedBeatmap;
|
||||
[Cached(typeof(IBindable<PlaylistItem>))]
|
||||
private readonly Bindable<PlaylistItem> currentItem = new Bindable<PlaylistItem>();
|
||||
|
||||
private MultiplayerPlaylist list = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private BeatmapInfo importedBeatmap = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
@ -198,7 +200,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
addItemStep();
|
||||
addItemStep();
|
||||
|
||||
DrawableRoomPlaylistItem[] drawableItems = null;
|
||||
DrawableRoomPlaylistItem[] drawableItems = null!;
|
||||
AddStep("get drawable items", () => drawableItems = this.ChildrenOfType<DrawableRoomPlaylistItem>().ToArray());
|
||||
|
||||
// Add 1 item for another user.
|
||||
|
@ -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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -28,13 +26,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneMultiplayerSpectateButton : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerSpectateButton spectateButton;
|
||||
private MatchStartControl startControl;
|
||||
[Cached(typeof(IBindable<PlaylistItem>))]
|
||||
private readonly Bindable<PlaylistItem> currentItem = new Bindable<PlaylistItem>();
|
||||
|
||||
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
|
||||
private MultiplayerSpectateButton spectateButton = null!;
|
||||
private MatchStartControl startControl = null!;
|
||||
|
||||
private BeatmapSetInfo importedSet;
|
||||
private BeatmapManager beatmaps;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
@ -52,14 +51,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("create button", () =>
|
||||
{
|
||||
AvailabilityTracker.SelectedItem.BindTo(selectedItem);
|
||||
AvailabilityTracker.SelectedItem.BindTo(currentItem);
|
||||
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
|
||||
selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
|
||||
{
|
||||
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
|
||||
};
|
||||
|
||||
currentItem.Value = SelectedRoom.Value.Playlist.First();
|
||||
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
|
@ -202,7 +202,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
Debug.Assert(joinedRoom.Playlist.Count > 0);
|
||||
|
||||
APIRoom.Playlist.Clear();
|
||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(item => new PlaylistItem(item)));
|
||||
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
|
||||
|
||||
// The server will null out the end date upon the host joining the room, but the null value is never communicated to the client.
|
||||
@ -734,7 +734,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.Playlist.Add(item);
|
||||
APIRoom.Playlist.Add(createPlaylistItem(item));
|
||||
APIRoom.Playlist.Add(new PlaylistItem(item));
|
||||
|
||||
ItemAdded?.Invoke(item);
|
||||
RoomUpdated?.Invoke();
|
||||
@ -780,7 +780,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
|
||||
|
||||
APIRoom.Playlist.RemoveAt(existingIndex);
|
||||
APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item));
|
||||
APIRoom.Playlist.Insert(existingIndex, new PlaylistItem(item));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -853,18 +853,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
RoomUpdated?.Invoke();
|
||||
}
|
||||
|
||||
private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem(new APIBeatmap { OnlineID = item.BeatmapID, StarRating = item.StarRating })
|
||||
{
|
||||
ID = item.ID,
|
||||
OwnerID = item.OwnerID,
|
||||
RulesetID = item.RulesetID,
|
||||
Expired = item.Expired,
|
||||
PlaylistOrder = item.PlaylistOrder,
|
||||
PlayedAt = item.PlayedAt,
|
||||
RequiredMods = item.RequiredMods.ToArray(),
|
||||
AllowedMods = item.AllowedMods.ToArray()
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.
|
||||
/// </summary>
|
||||
|
39
osu.Game/Online/Multiplayer/MultiplayerRoomExtensions.cs
Normal file
39
osu.Game/Online/Multiplayer/MultiplayerRoomExtensions.cs
Normal file
@ -0,0 +1,39 @@
|
||||
// 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.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public static class MultiplayerRoomExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all historical/expired items from the <paramref name="room"/>, in the order in which they were played.
|
||||
/// </summary>
|
||||
public static IEnumerable<MultiplayerPlaylistItem> GetHistoricalItems(this MultiplayerRoom room)
|
||||
=> room.Playlist.Where(item => item.Expired).OrderBy(item => item.PlayedAt);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all non-expired items from the <paramref name="room"/>, in the order in which they are to be played.
|
||||
/// </summary>
|
||||
public static IEnumerable<MultiplayerPlaylistItem> GetUpcomingItems(this MultiplayerRoom room)
|
||||
=> room.Playlist.Where(item => !item.Expired).OrderBy(item => item.PlaylistOrder);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first non-expired <see cref="MultiplayerPlaylistItem"/> in playlist order from the supplied <paramref name="room"/>,
|
||||
/// or the last-played <see cref="MultiplayerPlaylistItem"/> if all items are expired,
|
||||
/// or <see langword="null"/> if <paramref name="room"/> was empty.
|
||||
/// </summary>
|
||||
public static MultiplayerPlaylistItem? GetCurrentItem(this MultiplayerRoom room)
|
||||
{
|
||||
if (room.Playlist.Count == 0)
|
||||
return null;
|
||||
|
||||
return room.Playlist.All(item => item.Expired)
|
||||
? GetHistoricalItems(room).Last()
|
||||
: GetUpcomingItems(room).First();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,25 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public partial class RankRangePill : MultiplayerRoomComposite
|
||||
public partial class RankRangePill : CompositeDrawable
|
||||
{
|
||||
private OsuTextFlowContainer rankFlow;
|
||||
private OsuTextFlowContainer rankFlow = null!;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
public RankRangePill()
|
||||
{
|
||||
@ -55,20 +57,28 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
base.LoadComplete();
|
||||
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
rankFlow.Clear();
|
||||
|
||||
if (Room == null || Room.Users.All(u => u.User == null))
|
||||
if (client.Room == null || client.Room.Users.All(u => u.User == null))
|
||||
{
|
||||
rankFlow.AddText("-");
|
||||
return;
|
||||
}
|
||||
|
||||
int minRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Min();
|
||||
int maxRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Max();
|
||||
int minRank = client.Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Min();
|
||||
int maxRank = client.Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Max();
|
||||
|
||||
rankFlow.AddText("#");
|
||||
rankFlow.AddText(minRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||
@ -78,5 +88,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
rankFlow.AddText("#");
|
||||
rankFlow.AddText(maxRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
client.RoomUpdated -= onRoomUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,50 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.Countdown;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public partial class MatchStartControl : MultiplayerRoomComposite
|
||||
public partial class MatchStartControl : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable clickOperation;
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private IDialogOverlay dialogOverlay { get; set; }
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
|
||||
private Sample sampleReady;
|
||||
private Sample sampleReadyAll;
|
||||
private Sample sampleUnready;
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<PlaylistItem?> currentItem { get; set; } = null!;
|
||||
|
||||
private readonly MultiplayerReadyButton readyButton;
|
||||
private readonly MultiplayerCountdownButton countdownButton;
|
||||
|
||||
private IBindable<bool> operationInProgress = null!;
|
||||
private ScheduledDelegate? readySampleDelegate;
|
||||
private IDisposable? clickOperation;
|
||||
private Sample? sampleReady;
|
||||
private Sample? sampleReadyAll;
|
||||
private Sample? sampleUnready;
|
||||
private int countReady;
|
||||
private ScheduledDelegate readySampleDelegate;
|
||||
private IBindable<bool> operationInProgress;
|
||||
|
||||
public MatchStartControl()
|
||||
{
|
||||
@ -91,34 +94,29 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
CurrentPlaylistItem.BindValueChanged(_ => updateState());
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
currentItem.BindValueChanged(_ => updateState());
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
client.LoadRequested += onLoadRequested;
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override void OnRoomLoadRequested()
|
||||
{
|
||||
base.OnRoomLoadRequested();
|
||||
endOperation();
|
||||
}
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void onLoadRequested() => Scheduler.AddOnce(endOperation);
|
||||
|
||||
private void onReadyButtonClick()
|
||||
{
|
||||
if (Room == null)
|
||||
if (client.Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(clickOperation == null);
|
||||
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
if (Client.IsHost)
|
||||
if (client.IsHost)
|
||||
{
|
||||
if (Room.State == MultiplayerRoomState.Open)
|
||||
if (client.Room.State == MultiplayerRoomState.Open)
|
||||
{
|
||||
if (isReady() && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
|
||||
if (isReady() && !client.Room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
|
||||
startMatch();
|
||||
else
|
||||
toggleReady();
|
||||
@ -131,16 +129,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
dialogOverlay.Push(new ConfirmAbortDialog(abortMatch, endOperation));
|
||||
}
|
||||
}
|
||||
else if (Room.State != MultiplayerRoomState.Closed)
|
||||
else if (client.Room.State != MultiplayerRoomState.Closed)
|
||||
toggleReady();
|
||||
|
||||
bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating;
|
||||
bool isReady() => client.LocalUser?.State == MultiplayerUserState.Ready || client.LocalUser?.State == MultiplayerUserState.Spectating;
|
||||
|
||||
void toggleReady() => Client.ToggleReady().FireAndForget(
|
||||
void toggleReady() => client.ToggleReady().FireAndForget(
|
||||
onSuccess: endOperation,
|
||||
onError: _ => endOperation());
|
||||
|
||||
void startMatch() => Client.StartMatch().FireAndForget(onSuccess: () =>
|
||||
void startMatch() => client.StartMatch().FireAndForget(onSuccess: () =>
|
||||
{
|
||||
// gameplay is starting, the button will be unblocked on load requested.
|
||||
}, onError: _ =>
|
||||
@ -149,7 +147,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
endOperation();
|
||||
});
|
||||
|
||||
void abortMatch() => Client.AbortMatch().FireAndForget(endOperation, _ => endOperation());
|
||||
void abortMatch() => client.AbortMatch().FireAndForget(endOperation, _ => endOperation());
|
||||
}
|
||||
|
||||
private void startCountdown(TimeSpan duration)
|
||||
@ -157,19 +155,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
Debug.Assert(clickOperation == null);
|
||||
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
Client.SendMatchRequest(new StartMatchCountdownRequest { Duration = duration }).ContinueWith(_ => endOperation());
|
||||
client.SendMatchRequest(new StartMatchCountdownRequest { Duration = duration }).ContinueWith(_ => endOperation());
|
||||
}
|
||||
|
||||
private void cancelCountdown()
|
||||
{
|
||||
if (Client.Room == null)
|
||||
if (client.Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(clickOperation == null);
|
||||
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
MultiplayerCountdown countdown = Client.Room.ActiveCountdowns.Single(c => c is MatchStartCountdown);
|
||||
Client.SendMatchRequest(new StopCountdownRequest(countdown.ID)).ContinueWith(_ => endOperation());
|
||||
MultiplayerCountdown countdown = client.Room.ActiveCountdowns.Single(c => c is MatchStartCountdown);
|
||||
client.SendMatchRequest(new StopCountdownRequest(countdown.ID)).ContinueWith(_ => endOperation());
|
||||
}
|
||||
|
||||
private void endOperation()
|
||||
@ -180,19 +178,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (Room == null)
|
||||
if (client.Room == null)
|
||||
{
|
||||
readyButton.Enabled.Value = false;
|
||||
countdownButton.Enabled.Value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var localUser = Client.LocalUser;
|
||||
var localUser = client.LocalUser;
|
||||
|
||||
int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
||||
int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
||||
int newCountReady = client.Room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
||||
int newCountTotal = client.Room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
||||
|
||||
if (!Client.IsHost || Room.Settings.AutoStartEnabled)
|
||||
if (!client.IsHost || client.Room.Settings.AutoStartEnabled)
|
||||
countdownButton.Hide();
|
||||
else
|
||||
{
|
||||
@ -211,21 +209,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}
|
||||
|
||||
readyButton.Enabled.Value = countdownButton.Enabled.Value =
|
||||
Room.State != MultiplayerRoomState.Closed
|
||||
&& CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId
|
||||
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
|
||||
client.Room.State != MultiplayerRoomState.Closed
|
||||
&& currentItem.Value?.ID == client.Room.Settings.PlaylistItemId
|
||||
&& !client.Room.Playlist.Single(i => i.ID == client.Room.Settings.PlaylistItemId).Expired
|
||||
&& !operationInProgress.Value;
|
||||
|
||||
// When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready.
|
||||
if (localUser?.State == MultiplayerUserState.Spectating)
|
||||
readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown);
|
||||
readyButton.Enabled.Value &= client.IsHost && newCountReady > 0 && !client.Room.ActiveCountdowns.Any(c => c is MatchStartCountdown);
|
||||
|
||||
// When the local user is not the host, the button should only be enabled when no match is in progress.
|
||||
if (!Client.IsHost)
|
||||
readyButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open;
|
||||
if (!client.IsHost)
|
||||
readyButton.Enabled.Value &= client.Room.State == MultiplayerRoomState.Open;
|
||||
|
||||
// At all times, the countdown button should only be enabled when no match is in progress.
|
||||
countdownButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open;
|
||||
countdownButton.Enabled.Value &= client.Room.State == MultiplayerRoomState.Open;
|
||||
|
||||
if (newCountReady == countReady)
|
||||
return;
|
||||
@ -249,6 +247,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
{
|
||||
client.RoomUpdated -= onRoomUpdated;
|
||||
client.LoadRequested -= onLoadRequested;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ConfirmAbortDialog : DangerousActionDialog
|
||||
{
|
||||
public ConfirmAbortDialog(Action abortMatch, Action cancel)
|
||||
|
@ -5,7 +5,9 @@ using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
@ -17,7 +19,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public partial class MultiplayerSpectateButton : MultiplayerRoomComposite
|
||||
public partial class MultiplayerSpectateButton : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
|
||||
@ -25,6 +27,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<PlaylistItem?> currentItem { get; set; } = null!;
|
||||
|
||||
private IBindable<bool> operationInProgress = null!;
|
||||
|
||||
private readonly RoundedButton button;
|
||||
@ -44,7 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
var clickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
Client.ToggleSpectate().ContinueWith(_ => endOperation());
|
||||
client.ToggleSpectate().ContinueWith(_ => endOperation());
|
||||
|
||||
void endOperation() => clickOperation?.Dispose();
|
||||
}
|
||||
@ -63,19 +71,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
CurrentPlaylistItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true);
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
|
||||
currentItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true);
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
switch (Client.LocalUser?.State)
|
||||
switch (client.LocalUser?.State)
|
||||
{
|
||||
default:
|
||||
button.Text = "Spectate";
|
||||
@ -88,8 +93,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
break;
|
||||
}
|
||||
|
||||
button.Enabled.Value = Client.Room != null
|
||||
&& Client.Room.State != MultiplayerRoomState.Closed
|
||||
button.Enabled.Value = client.Room != null
|
||||
&& client.Room.State != MultiplayerRoomState.Closed
|
||||
&& !operationInProgress.Value;
|
||||
|
||||
Scheduler.AddOnce(checkForAutomaticDownload);
|
||||
@ -112,11 +117,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
private void checkForAutomaticDownload()
|
||||
{
|
||||
PlaylistItem? currentItem = CurrentPlaylistItem.Value;
|
||||
PlaylistItem? item = currentItem.Value;
|
||||
|
||||
downloadCheckCancellation?.Cancel();
|
||||
|
||||
if (currentItem == null)
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
if (!automaticallyDownload.Value)
|
||||
@ -128,13 +133,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
//
|
||||
// Rather than over-complicating this flow, let's only auto-download when spectating for the time being.
|
||||
// A potential path forward would be to have a local auto-download checkbox above the playlist item list area.
|
||||
if (Client.LocalUser?.State != MultiplayerUserState.Spectating)
|
||||
if (client.LocalUser?.State != MultiplayerUserState.Spectating)
|
||||
return;
|
||||
|
||||
// In a perfect world we'd use BeatmapAvailability, but there's no event-driven flow for when a selection changes.
|
||||
// ie. if selection changes from "not downloaded" to another "not downloaded" we wouldn't get a value changed raised.
|
||||
beatmapLookupCache
|
||||
.GetBeatmapAsync(currentItem.Beatmap.OnlineID, (downloadCheckCancellation = new CancellationTokenSource()).Token)
|
||||
.GetBeatmapAsync(item.Beatmap.OnlineID, (downloadCheckCancellation = new CancellationTokenSource()).Token)
|
||||
.ContinueWith(resolved => Schedule(() =>
|
||||
{
|
||||
var beatmapSet = resolved.GetResultSafely()?.BeatmapSet;
|
||||
@ -150,5 +155,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
client.RoomUpdated -= onRoomUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@ -17,18 +15,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
/// <summary>
|
||||
/// The multiplayer playlist, containing lists to show the items from a <see cref="MultiplayerRoom"/> in both gameplay-order and historical-order.
|
||||
/// </summary>
|
||||
public partial class MultiplayerPlaylist : MultiplayerRoomComposite
|
||||
public partial class MultiplayerPlaylist : CompositeDrawable
|
||||
{
|
||||
public readonly Bindable<MultiplayerPlaylistDisplayMode> DisplayMode = new Bindable<MultiplayerPlaylistDisplayMode>();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when an item requests to be edited.
|
||||
/// </summary>
|
||||
public Action<PlaylistItem> RequestEdit;
|
||||
public Action<PlaylistItem>? RequestEdit;
|
||||
|
||||
private MultiplayerPlaylistTabControl playlistTabControl;
|
||||
private MultiplayerQueueList queueList;
|
||||
private MultiplayerHistoryList historyList;
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<PlaylistItem?> currentItem { get; set; } = null!;
|
||||
|
||||
private MultiplayerPlaylistTabControl playlistTabControl = null!;
|
||||
private MultiplayerQueueList queueList = null!;
|
||||
private MultiplayerHistoryList historyList = null!;
|
||||
private bool firstPopulation = true;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -54,14 +58,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
queueList = new MultiplayerQueueList
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
SelectedItem = { BindTarget = CurrentPlaylistItem },
|
||||
SelectedItem = { BindTarget = currentItem },
|
||||
RequestEdit = item => RequestEdit?.Invoke(item)
|
||||
},
|
||||
historyList = new MultiplayerHistoryList
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
SelectedItem = { BindTarget = CurrentPlaylistItem }
|
||||
SelectedItem = { BindTarget = currentItem }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,7 +77,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
DisplayMode.BindValueChanged(onDisplayModeChanged, true);
|
||||
client.ItemAdded += playlistItemAdded;
|
||||
client.ItemRemoved += playlistItemRemoved;
|
||||
client.ItemChanged += playlistItemChanged;
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void onDisplayModeChanged(ValueChangedEvent<MultiplayerPlaylistDisplayMode> mode)
|
||||
@ -82,11 +92,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
queueList.FadeTo(mode.NewValue == MultiplayerPlaylistDisplayMode.Queue ? 1 : 0, 100);
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
if (Room == null)
|
||||
private void updateState()
|
||||
{
|
||||
if (client.Room == null)
|
||||
{
|
||||
historyList.Items.Clear();
|
||||
queueList.Items.Clear();
|
||||
@ -96,34 +106,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
|
||||
if (firstPopulation)
|
||||
{
|
||||
foreach (var item in Room.Playlist)
|
||||
foreach (var item in client.Room.Playlist)
|
||||
addItemToLists(item);
|
||||
|
||||
firstPopulation = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PlaylistItemAdded(MultiplayerPlaylistItem item)
|
||||
{
|
||||
base.PlaylistItemAdded(item);
|
||||
addItemToLists(item);
|
||||
}
|
||||
private void playlistItemAdded(MultiplayerPlaylistItem item) => Schedule(() => addItemToLists(item));
|
||||
|
||||
protected override void PlaylistItemRemoved(long item)
|
||||
{
|
||||
base.PlaylistItemRemoved(item);
|
||||
removeItemFromLists(item);
|
||||
}
|
||||
private void playlistItemRemoved(long item) => Schedule(() => removeItemFromLists(item));
|
||||
|
||||
protected override void PlaylistItemChanged(MultiplayerPlaylistItem item)
|
||||
private void playlistItemChanged(MultiplayerPlaylistItem item) => Schedule(() =>
|
||||
{
|
||||
base.PlaylistItemChanged(item);
|
||||
if (client.Room == null)
|
||||
return;
|
||||
|
||||
var newApiItem = Playlist.SingleOrDefault(i => i.ID == item.ID);
|
||||
var newApiItem = new PlaylistItem(item);
|
||||
var existingApiItemInQueue = queueList.Items.SingleOrDefault(i => i.ID == item.ID);
|
||||
|
||||
// Test if the only change between the two playlist items is the order.
|
||||
if (newApiItem != null && existingApiItemInQueue != null && existingApiItemInQueue.With(playlistOrder: newApiItem.PlaylistOrder).Equals(newApiItem))
|
||||
if (existingApiItemInQueue != null && existingApiItemInQueue.With(playlistOrder: newApiItem.PlaylistOrder).Equals(newApiItem))
|
||||
{
|
||||
// Set the new playlist order directly without refreshing the DrawablePlaylistItem.
|
||||
existingApiItemInQueue.PlaylistOrder = newApiItem.PlaylistOrder;
|
||||
@ -137,20 +140,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
removeItemFromLists(item.ID);
|
||||
addItemToLists(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private void addItemToLists(MultiplayerPlaylistItem item)
|
||||
{
|
||||
var apiItem = Playlist.SingleOrDefault(i => i.ID == item.ID);
|
||||
var apiItem = client.Room?.Playlist.SingleOrDefault(i => i.ID == item.ID);
|
||||
|
||||
// Item could have been removed from the playlist while the local player was in gameplay.
|
||||
if (apiItem == null)
|
||||
return;
|
||||
|
||||
if (item.Expired)
|
||||
historyList.Items.Add(apiItem);
|
||||
historyList.Items.Add(new PlaylistItem(apiItem));
|
||||
else
|
||||
queueList.Items.Add(apiItem);
|
||||
queueList.Items.Add(new PlaylistItem(apiItem));
|
||||
}
|
||||
|
||||
private void removeItemFromLists(long item)
|
||||
|
@ -1,125 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public abstract partial class MultiplayerRoomComposite : OnlinePlayComposite
|
||||
{
|
||||
[CanBeNull]
|
||||
protected MultiplayerRoom Room => Client.Room;
|
||||
|
||||
[Resolved]
|
||||
protected MultiplayerClient Client { get; private set; }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Client.RoomUpdated += invokeOnRoomUpdated;
|
||||
Client.LoadRequested += invokeOnRoomLoadRequested;
|
||||
Client.UserLeft += invokeUserLeft;
|
||||
Client.UserKicked += invokeUserKicked;
|
||||
Client.UserJoined += invokeUserJoined;
|
||||
Client.ItemAdded += invokeItemAdded;
|
||||
Client.ItemRemoved += invokeItemRemoved;
|
||||
Client.ItemChanged += invokeItemChanged;
|
||||
|
||||
OnRoomUpdated();
|
||||
}
|
||||
|
||||
private void invokeOnRoomUpdated() => Scheduler.AddOnce(OnRoomUpdated);
|
||||
private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.Add(() => UserJoined(user));
|
||||
private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.Add(() => UserKicked(user));
|
||||
private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.Add(() => UserLeft(user));
|
||||
private void invokeItemAdded(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemAdded(item));
|
||||
private void invokeItemRemoved(long item) => Schedule(() => PlaylistItemRemoved(item));
|
||||
private void invokeItemChanged(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemChanged(item));
|
||||
private void invokeOnRoomLoadRequested() => Scheduler.AddOnce(OnRoomLoadRequested);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a user has joined the room.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
protected virtual void UserJoined(MultiplayerRoomUser user)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a user has been kicked from the room (including the local user).
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
protected virtual void UserKicked(MultiplayerRoomUser user)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a user has left the room.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
protected virtual void UserLeft(MultiplayerRoomUser user)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a playlist item is added to the room.
|
||||
/// </summary>
|
||||
/// <param name="item">The added playlist item.</param>
|
||||
protected virtual void PlaylistItemAdded(MultiplayerPlaylistItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a playlist item is removed from the room.
|
||||
/// </summary>
|
||||
/// <param name="item">The ID of the removed playlist item.</param>
|
||||
protected virtual void PlaylistItemRemoved(long item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a playlist item is changed in the room.
|
||||
/// </summary>
|
||||
/// <param name="item">The new playlist item, with an existing item's ID.</param>
|
||||
protected virtual void PlaylistItemChanged(MultiplayerPlaylistItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when any change occurs to the multiplayer room.
|
||||
/// </summary>
|
||||
protected virtual void OnRoomUpdated()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the room requests the local user to load into gameplay.
|
||||
/// </summary>
|
||||
protected virtual void OnRoomLoadRequested()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
Client.RoomUpdated -= invokeOnRoomUpdated;
|
||||
Client.LoadRequested -= invokeOnRoomLoadRequested;
|
||||
Client.UserLeft -= invokeUserLeft;
|
||||
Client.UserKicked -= invokeUserKicked;
|
||||
Client.UserJoined -= invokeUserJoined;
|
||||
Client.ItemAdded -= invokeItemAdded;
|
||||
Client.ItemRemoved -= invokeItemRemoved;
|
||||
Client.ItemChanged -= invokeItemChanged;
|
||||
}
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,26 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public partial class MultiplayerRoomSounds : MultiplayerRoomComposite
|
||||
public partial class MultiplayerRoomSounds : CompositeDrawable
|
||||
{
|
||||
private Sample hostChangedSample;
|
||||
private Sample userJoinedSample;
|
||||
private Sample userLeftSample;
|
||||
private Sample userKickedSample;
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private Sample? hostChangedSample;
|
||||
private Sample? userJoinedSample;
|
||||
private Sample? userLeftSample;
|
||||
private Sample? userKickedSample;
|
||||
private MultiplayerRoomUser? host;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
@ -32,36 +35,47 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Host.BindValueChanged(hostChanged);
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
client.UserJoined += onUserJoined;
|
||||
client.UserLeft += onUserLeft;
|
||||
client.UserKicked += onUserKicked;
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override void UserJoined(MultiplayerRoomUser user)
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
base.UserJoined(user);
|
||||
if (EqualityComparer<MultiplayerRoomUser>.Default.Equals(host, client.Room?.Host))
|
||||
return;
|
||||
|
||||
Scheduler.AddOnce(() => userJoinedSample?.Play());
|
||||
}
|
||||
|
||||
protected override void UserLeft(MultiplayerRoomUser user)
|
||||
{
|
||||
base.UserLeft(user);
|
||||
|
||||
Scheduler.AddOnce(() => userLeftSample?.Play());
|
||||
}
|
||||
|
||||
protected override void UserKicked(MultiplayerRoomUser user)
|
||||
{
|
||||
base.UserKicked(user);
|
||||
|
||||
Scheduler.AddOnce(() => userKickedSample?.Play());
|
||||
}
|
||||
|
||||
private void hostChanged(ValueChangedEvent<APIUser> value)
|
||||
{
|
||||
// only play sound when the host changes from an already-existing host.
|
||||
if (value.OldValue == null) return;
|
||||
if (host != null)
|
||||
Scheduler.AddOnce(() => hostChangedSample?.Play());
|
||||
|
||||
Scheduler.AddOnce(() => hostChangedSample?.Play());
|
||||
host = client.Room?.Host;
|
||||
}
|
||||
|
||||
private void onUserJoined(MultiplayerRoomUser user)
|
||||
=> Scheduler.AddOnce(() => userJoinedSample?.Play());
|
||||
|
||||
private void onUserLeft(MultiplayerRoomUser user)
|
||||
=> Scheduler.AddOnce(() => userLeftSample?.Play());
|
||||
|
||||
private void onUserKicked(MultiplayerRoomUser user)
|
||||
=> Scheduler.AddOnce(() => userKickedSample?.Play());
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
{
|
||||
client.RoomUpdated -= onRoomUpdated;
|
||||
client.UserJoined -= onUserJoined;
|
||||
client.UserLeft -= onUserLeft;
|
||||
client.UserKicked -= onUserKicked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -30,7 +31,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
{
|
||||
public partial class ParticipantPanel : MultiplayerRoomComposite, IHasContextMenu
|
||||
public partial class ParticipantPanel : CompositeDrawable, IHasContextMenu
|
||||
{
|
||||
public readonly MultiplayerRoomUser User;
|
||||
|
||||
@ -40,6 +41,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
[Resolved]
|
||||
private IRulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private SpriteIcon crown = null!;
|
||||
|
||||
private OsuSpriteText userRankText = null!;
|
||||
@ -171,23 +175,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding(4),
|
||||
Action = () => Client.KickUser(User.UserID).FireAndForget(),
|
||||
Action = () => client.KickUser(User.UserID).FireAndForget(),
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
base.LoadComplete();
|
||||
|
||||
if (Room == null || Client.LocalUser == null)
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (client.Room == null || client.LocalUser == null)
|
||||
return;
|
||||
|
||||
const double fade_time = 50;
|
||||
|
||||
var currentItem = Playlist.GetCurrentItem();
|
||||
MultiplayerPlaylistItem? currentItem = client.Room.GetCurrentItem();
|
||||
Ruleset? ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null;
|
||||
|
||||
int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null;
|
||||
@ -200,8 +212,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
else
|
||||
userModsDisplay.FadeOut(fade_time);
|
||||
|
||||
kickButton.Alpha = Client.IsHost && !User.Equals(Client.LocalUser) ? 1 : 0;
|
||||
crown.Alpha = Room.Host?.Equals(User) == true ? 1 : 0;
|
||||
kickButton.Alpha = client.IsHost && !User.Equals(client.LocalUser) ? 1 : 0;
|
||||
crown.Alpha = client.Room.Host?.Equals(User) == true ? 1 : 0;
|
||||
|
||||
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
|
||||
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
|
||||
@ -215,7 +227,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Room == null)
|
||||
if (client.Room == null)
|
||||
return null;
|
||||
|
||||
// If the local user is targetted.
|
||||
@ -223,7 +235,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
return null;
|
||||
|
||||
// If the local user is not the host of the room.
|
||||
if (Room.Host?.UserID != api.LocalUser.Value.Id)
|
||||
if (client.Room.Host?.UserID != api.LocalUser.Value.Id)
|
||||
return null;
|
||||
|
||||
int targetUser = User.UserID;
|
||||
@ -233,23 +245,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
|
||||
{
|
||||
// Ensure the local user is still host.
|
||||
if (!Client.IsHost)
|
||||
if (!client.IsHost)
|
||||
return;
|
||||
|
||||
Client.TransferHost(targetUser).FireAndForget();
|
||||
client.TransferHost(targetUser).FireAndForget();
|
||||
}),
|
||||
new OsuMenuItem("Kick", MenuItemType.Destructive, () =>
|
||||
{
|
||||
// Ensure the local user is still host.
|
||||
if (!Client.IsHost)
|
||||
if (!client.IsHost)
|
||||
return;
|
||||
|
||||
Client.KickUser(targetUser).FireAndForget();
|
||||
client.KickUser(targetUser).FireAndForget();
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
client.RoomUpdated -= onRoomUpdated;
|
||||
}
|
||||
|
||||
public partial class KickButton : IconButton
|
||||
{
|
||||
public KickButton()
|
||||
|
@ -1,24 +1,24 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
{
|
||||
public partial class ParticipantsList : MultiplayerRoomComposite
|
||||
public partial class ParticipantsList : CompositeDrawable
|
||||
{
|
||||
private FillFlowContainer<ParticipantPanel> panels;
|
||||
private FillFlowContainer<ParticipantPanel> panels = null!;
|
||||
private ParticipantPanel? currentHostPanel;
|
||||
|
||||
[CanBeNull]
|
||||
private ParticipantPanel currentHostPanel;
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -37,11 +37,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
base.LoadComplete();
|
||||
|
||||
if (Room == null)
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (client.Room == null)
|
||||
panels.Clear();
|
||||
else
|
||||
{
|
||||
@ -49,15 +57,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
foreach (var p in panels)
|
||||
{
|
||||
// Note that we *must* use reference equality here, as this call is scheduled and a user may have left and joined since it was last run.
|
||||
if (Room.Users.All(u => !ReferenceEquals(p.User, u)))
|
||||
if (client.Room.Users.All(u => !ReferenceEquals(p.User, u)))
|
||||
p.Expire();
|
||||
}
|
||||
|
||||
// Add panels for all users new to the room.
|
||||
foreach (var user in Room.Users.Except(panels.Select(p => p.User)))
|
||||
foreach (var user in client.Room.Users.Except(panels.Select(p => p.User)))
|
||||
panels.Add(new ParticipantPanel(user));
|
||||
|
||||
if (currentHostPanel == null || !currentHostPanel.User.Equals(Room.Host))
|
||||
if (currentHostPanel == null || !currentHostPanel.User.Equals(client.Room.Host))
|
||||
{
|
||||
// Reset position of previous host back to normal, if one existing.
|
||||
if (currentHostPanel != null && panels.Contains(currentHostPanel))
|
||||
@ -66,9 +74,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
currentHostPanel = null;
|
||||
|
||||
// Change position of new host to display above all participants.
|
||||
if (Room.Host != null)
|
||||
if (client.Room.Host != null)
|
||||
{
|
||||
currentHostPanel = panels.SingleOrDefault(u => u.User.Equals(Room.Host));
|
||||
currentHostPanel = panels.SingleOrDefault(u => u.User.Equals(client.Room.Host));
|
||||
|
||||
if (currentHostPanel != null)
|
||||
panels.SetLayoutPosition(currentHostPanel, -1);
|
||||
@ -76,5 +84,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
client.RoomUpdated -= onRoomUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -20,27 +19,26 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
{
|
||||
internal partial class TeamDisplay : MultiplayerRoomComposite
|
||||
internal partial class TeamDisplay : CompositeDrawable
|
||||
{
|
||||
private readonly MultiplayerRoomUser user;
|
||||
|
||||
private Drawable box;
|
||||
|
||||
private Sample sampleTeamSwap;
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private OsuClickableContainer clickableContent;
|
||||
private OsuClickableContainer clickableContent = null!;
|
||||
private Drawable box = null!;
|
||||
private Sample? sampleTeamSwap;
|
||||
|
||||
public TeamDisplay(MultiplayerRoomUser user)
|
||||
{
|
||||
this.user = user;
|
||||
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
AutoSizeAxes = Axes.X;
|
||||
|
||||
Margin = new MarginPadding { Horizontal = 3 };
|
||||
}
|
||||
|
||||
@ -71,7 +69,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
}
|
||||
};
|
||||
|
||||
if (Client.LocalUser?.Equals(user) == true)
|
||||
if (client.LocalUser?.Equals(user) == true)
|
||||
{
|
||||
clickableContent.Action = changeTeam;
|
||||
clickableContent.TooltipText = "Change team";
|
||||
@ -80,23 +78,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void changeTeam()
|
||||
{
|
||||
Client.SendMatchRequest(new ChangeTeamRequest
|
||||
client.SendMatchRequest(new ChangeTeamRequest
|
||||
{
|
||||
TeamID = ((Client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
|
||||
TeamID = ((client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
|
||||
}).FireAndForget();
|
||||
}
|
||||
|
||||
public int? DisplayedTeam { get; private set; }
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
// we don't have a way of knowing when an individual user's state has updated, so just handle on RoomUpdated for now.
|
||||
|
||||
var userRoomState = Room?.Users.FirstOrDefault(u => u.Equals(user))?.MatchState;
|
||||
var userRoomState = client.Room?.Users.FirstOrDefault(u => u.Equals(user))?.MatchState;
|
||||
|
||||
const double duration = 400;
|
||||
|
||||
@ -138,5 +144,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
return colours.Blue;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
client.RoomUpdated -= onRoomUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user