1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-17 05:52:36 +08:00

Merge pull request #33241 from bdach/participant-list-transform-spam

Implement list virtualisation in multiplayer participants list
This commit is contained in:
Dean Herbert
2025-05-26 23:58:27 +09:00
committed by GitHub
Unverified
4 changed files with 190 additions and 100 deletions
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
@@ -15,6 +16,7 @@ using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Mods;
@@ -24,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -41,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestAddUser()
{
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.Current.Value).Distinct().Count() == 1);
AddStep("add user", () => MultiplayerClient.AddUser(new APIUser
{
@@ -50,20 +53,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
CoverUrl = TestResources.COVER_IMAGE_3,
}));
AddAssert("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
AddAssert("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.Current.Value).Distinct().Count() == 2);
}
[Test]
public void TestAddUnresolvedUser()
{
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.Current.Value).Distinct().Count() == 1);
AddStep("add non-resolvable user", () => MultiplayerClient.TestAddUnresolvedUser());
AddUntilStep("null user added", () => MultiplayerClient.ClientRoom.AsNonNull().Users.Count(u => u.User == null) == 1);
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.Current.Value).Distinct().Count() == 2);
AddStep("kick null user", () => this.ChildrenOfType<ParticipantPanel>().Single(p => p.User.User == null)
AddStep("kick null user", () => this.ChildrenOfType<ParticipantPanel>().Single(p => p.Current.Value.User == null)
.ChildrenOfType<ParticipantPanel.KickButton>().Single().TriggerClick());
AddUntilStep("null user kicked", () => MultiplayerClient.ClientRoom.AsNonNull().Users.Count == 1);
@@ -86,7 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("remove host", () => MultiplayerClient.RemoveUser(API.LocalUser.Value));
AddAssert("single panel is for second user", () => this.ChildrenOfType<ParticipantPanel>().Single().User.UserID == secondUser?.Id);
AddAssert("single panel is for second user", () => this.ChildrenOfType<ParticipantPanel>().Single().Current.Value.UserID == secondUser?.Id);
}
[Test]
@@ -122,7 +125,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddRepeatStep("increment progress", () =>
{
float progress = this.ChildrenOfType<ParticipantPanel>().Single().User.BeatmapAvailability.DownloadProgress ?? 0;
float progress = this.ChildrenOfType<ParticipantPanel>().Single().Current.Value.BeatmapAvailability.DownloadProgress ?? 0;
MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f)));
}, 25);
@@ -163,13 +166,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
CoverUrl = TestResources.COVER_IMAGE_3,
}));
AddUntilStep("first user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(0).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
AddUntilStep("second user crown hidden", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 0);
AddUntilStep("first user crown visible",
() => this.ChildrenOfType<ParticipantPanel>().Single(p => p.Current.Value.UserID == 1001).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
AddUntilStep("second user crown hidden",
() => this.ChildrenOfType<ParticipantPanel>().Single(p => p.Current.Value.UserID == 3).ChildrenOfType<SpriteIcon>().First().Alpha == 0);
AddStep("make second user host", () => MultiplayerClient.TransferHost(3));
AddUntilStep("first user crown hidden", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(0).ChildrenOfType<SpriteIcon>().First().Alpha == 0);
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
AddUntilStep("first user crown visible",
() => this.ChildrenOfType<ParticipantPanel>().Single(p => p.Current.Value.UserID == 1001).ChildrenOfType<SpriteIcon>().First().Alpha == 0);
AddUntilStep("second user crown hidden",
() => this.ChildrenOfType<ParticipantPanel>().Single(p => p.Current.Value.UserID == 3).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
}
[Test]
@@ -185,9 +192,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("make second user host", () => MultiplayerClient.TransferHost(3));
AddAssert("second user above first", () =>
{
var first = this.ChildrenOfType<ParticipantPanel>().ElementAt(0);
var second = this.ChildrenOfType<ParticipantPanel>().ElementAt(1);
return second.Y < first.Y;
var first = this.ChildrenOfType<ParticipantPanel>().Single(u => u.Current.Value.UserID == 1001);
var second = this.ChildrenOfType<ParticipantPanel>().Single(u => u.Current.Value.UserID == 3);
return second.ScreenSpaceDrawQuad.TopLeft.Y < first.ScreenSpaceDrawQuad.TopLeft.Y;
});
}
@@ -230,7 +237,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestManyUsers()
{
const int users_count = 20;
const int users_count = 200;
AddStep("add many users", () =>
{
@@ -276,6 +283,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddRepeatStep("switch hosts", () => MultiplayerClient.TransferHost(RNG.Next(0, users_count)), 10);
AddStep("give host back", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id));
AddRepeatStep("perform many random state changes at once", () =>
{
for (int i = 0; i < users_count; ++i)
{
MultiplayerClient.ChangeUserBeatmapAvailability(i, BeatmapAvailability.LocallyAvailable());
MultiplayerClient.ChangeUserState(i, RNG.NextBool() ? MultiplayerUserState.Idle : MultiplayerUserState.Ready);
}
}, 100);
}
[Test]
@@ -434,6 +450,35 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
[Test]
public void TestTeams()
{
AddStep("enable teams", () => MultiplayerClient.ChangeSettings(matchType: MatchType.TeamVersus));
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.Current.Value).Distinct().Count() == 1);
int id = 3;
AddRepeatStep("add users", () => MultiplayerClient.AddUser(new APIUser
{
Id = Interlocked.Increment(ref id),
Username = "Second",
CoverUrl = TestResources.COVER_IMAGE_3,
}), 5);
AddAssert("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.Current.Value).Distinct().Count() == 6);
AddAssert("user 1001 on red team",
() => (MultiplayerClient.ClientRoom!.Users.Single(u => u.UserID == 1001).MatchState as TeamVersusUserState)?.TeamID,
() => Is.EqualTo(0));
AddStep("click first team indicator", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<TeamDisplay>().First());
InputManager.Click(MouseButton.Left);
});
AddAssert("user 1001 on blue team",
() => (MultiplayerClient.ClientRoom!.Users.Single(u => u.UserID == 1001).MatchState as TeamVersusUserState)?.TeamID,
() => Is.EqualTo(1));
}
private void createNewParticipantsList()
{
ParticipantsList? participantsList = null;
@@ -7,12 +7,13 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
@@ -36,9 +37,17 @@ using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
public partial class ParticipantPanel : CompositeDrawable, IHasContextMenu
public partial class ParticipantPanel : PoolableDrawable, IHasContextMenu, IHasCurrentValue<MultiplayerRoomUser>
{
public readonly MultiplayerRoomUser User;
public const int HEIGHT = 40;
public Bindable<MultiplayerRoomUser> Current
{
get => current.Current;
set => current.Current = value;
}
private readonly BindableWithCurrent<MultiplayerRoomUser> current = new BindableWithCurrent<MultiplayerRoomUser>(new MultiplayerRoomUser(-1));
[Resolved]
private IAPIProvider api { get; set; } = null!;
@@ -51,6 +60,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private SpriteIcon crown = null!;
private UserCoverBackground userCover = null!;
private UpdateableAvatar userAvatar = null!;
private OsuSpriteText username = null!;
private Container teamFlagContainer = null!;
private OsuSpriteText userRankText = null!;
private StyleDisplayIcon userStyleDisplay = null!;
private ModDisplay userModsDisplay = null!;
@@ -58,19 +71,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private IconButton kickButton = null!;
public ParticipantPanel(MultiplayerRoomUser user)
public ParticipantPanel()
{
User = user;
RelativeSizeAxes = Axes.X;
Height = 40;
Height = HEIGHT;
}
[BackgroundDependencyLoader]
private void load()
{
var user = User.User;
var backgroundColour = Color4Extensions.FromHex("#33413C");
InternalChild = new GridContainer
@@ -96,7 +105,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Colour = Color4Extensions.FromHex("#F7E65D"),
Alpha = 0
},
new TeamDisplay(User),
new TeamDisplay { Current = Current },
new Container
{
RelativeSizeAxes = Axes.Both,
@@ -109,13 +118,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
RelativeSizeAxes = Axes.Both,
Colour = backgroundColour
},
new UserCoverBackground
userCover = new UserCoverBackground
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Both,
Width = 0.75f,
User = user,
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.25f))
},
new FillFlowContainer
@@ -125,33 +133,30 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new UpdateableAvatar
userAvatar = new UpdateableAvatar
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
User = user
},
new UpdateableFlag
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(28, 20),
CountryCode = user?.CountryCode ?? default
},
new UpdateableTeamFlag(user?.Team)
teamFlagContainer = new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(40, 20),
},
new OsuSpriteText
username = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
Text = user?.Username ?? string.Empty
},
userRankText = new OsuSpriteText
{
@@ -198,18 +203,50 @@ 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(current.Value.UserID).FireAndForget(),
},
},
}
};
}
protected override void LoadComplete()
protected override void PrepareForUse()
{
base.LoadComplete();
base.PrepareForUse();
client.RoomUpdated += onRoomUpdated;
updateUser();
FinishTransforms(true);
}
protected override void FreeAfterUse()
{
base.FreeAfterUse();
client.RoomUpdated -= onRoomUpdated;
// this is a safety measure.
// `MultiplayerRoomUser` has equality members overridden to compare by `UserID` only.
// `MultiplayerClient` only delivers updates of fields values to specific object references.
// if this operation is not done here, in a scenario wherein a user quits and rejoins a room,
// it is possible for a single poolable panel to be freed and then used for the same user with the same ID,
// which at bindable level will lead to `current` not changing (because of the overridden equality member),
// which will lead to this instance not showing any updates for the user in question
// because it's associated with an object reference that `MultiplayerClient` is no longer updating.
current.SetDefault();
}
private void updateUser()
{
var user = current.Value.User;
userCover.User = user;
userAvatar.User = user;
teamFlagContainer.Child = new UpdateableTeamFlag(user?.Team)
{
Size = new Vector2(40, 20)
};
username.Text = user?.Username ?? string.Empty;
updateState();
}
@@ -222,13 +259,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
const double fade_time = 50;
var user = current.Value;
if (client.Room.GetCurrentItem() is MultiplayerPlaylistItem currentItem)
{
int userBeatmapId = User.BeatmapId ?? currentItem.BeatmapID;
int userRulesetId = User.RulesetId ?? currentItem.RulesetID;
int userBeatmapId = user.BeatmapId ?? currentItem.BeatmapID;
int userRulesetId = user.RulesetId ?? currentItem.RulesetID;
Ruleset? userRuleset = rulesets.GetRuleset(userRulesetId)?.CreateInstance();
int? currentModeRank = userRuleset == null ? null : User.User?.RulesetsStatistics?.GetValueOrDefault(userRuleset.ShortName)?.GlobalRank;
int? currentModeRank = userRuleset == null ? null : user.User?.RulesetsStatistics?.GetValueOrDefault(userRuleset.ShortName)?.GlobalRank;
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
if (userBeatmapId == currentItem.BeatmapID && userRulesetId == currentItem.RulesetID)
@@ -238,12 +277,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
// 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.
Schedule(() => userModsDisplay.Current.Value = userRuleset == null ? Array.Empty<Mod>() : User.Mods.Select(m => m.ToMod(userRuleset)).ToList());
Schedule(() => userModsDisplay.Current.Value = userRuleset == null ? Array.Empty<Mod>() : user.Mods.Select(m => m.ToMod(userRuleset)).ToList());
}
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
userStateDisplay.UpdateStatus(user.State, user.BeatmapAvailability);
if (User.BeatmapAvailability.State == DownloadState.LocallyAvailable && User.State != MultiplayerUserState.Spectating)
if (user.BeatmapAvailability.State == DownloadState.LocallyAvailable && user.State != MultiplayerUserState.Spectating)
{
userModsDisplay.FadeIn(fade_time);
userStyleDisplay.FadeIn(fade_time);
@@ -254,8 +293,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
userStyleDisplay.FadeOut(fade_time);
}
kickButton.Alpha = client.IsHost && !User.Equals(client.LocalUser) ? 1 : 0;
crown.Alpha = client.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;
}
public MenuItem[]? ContextMenuItems
@@ -265,15 +304,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
if (client.Room == null)
return null;
var user = current.Value;
// If the local user is targetted.
if (User.UserID == api.LocalUser.Value.Id)
if (user.UserID == api.LocalUser.Value.Id)
return null;
// If the local user is not the host of the room.
if (client.Room.Host?.UserID != api.LocalUser.Value.Id)
return null;
int targetUser = User.UserID;
int targetUser = user.UserID;
return new MenuItem[]
{
@@ -297,14 +338,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (client.IsNotNull())
client.RoomUpdated -= onRoomUpdated;
}
public partial class KickButton : IconButton
{
public KickButton()
@@ -3,40 +3,34 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
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 : CompositeDrawable
public partial class ParticipantsList : VirtualisedListContainer<MultiplayerRoomUser, ParticipantPanel>
{
private FillFlowContainer<ParticipantPanel> panels = null!;
private ParticipantPanel? currentHostPanel;
private BindableList<MultiplayerRoomUser> participants => RowData;
private MultiplayerRoomUser? currentHost;
[Resolved]
private MultiplayerClient client { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
public ParticipantsList()
: base(ParticipantPanel.HEIGHT, initialPoolSize: 20)
{
InternalChild = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
Child = panels = new FillFlowContainer<ParticipantPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 2)
}
};
}
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer
{
ScrollbarVisible = false,
};
protected override void LoadComplete()
{
base.LoadComplete();
@@ -50,36 +44,38 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private void updateState()
{
if (client.Room == null)
panels.Clear();
participants.Clear();
else
{
// Remove panels for users no longer in the room.
foreach (var p in panels)
for (int i = participants.Count - 1; i >= 0; i--)
{
var participant = participants[i];
// 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 (client.Room.Users.All(u => !ReferenceEquals(p.User, u)))
p.Expire();
if (client.Room.Users.All(u => !ReferenceEquals(participant, u)))
participants.RemoveAt(i);
}
// Add panels for all users new to the room.
foreach (var user in client.Room.Users.Except(panels.Select(p => p.User)))
panels.Add(new ParticipantPanel(user));
foreach (var user in client.Room.Users.Except(participants))
participants.Add(user);
if (currentHostPanel == null || !currentHostPanel.User.Equals(client.Room.Host))
if (currentHost == null || !currentHost.Equals(client.Room.Host))
{
// Reset position of previous host back to normal, if one existing.
if (currentHostPanel != null && panels.Contains(currentHostPanel))
panels.SetLayoutPosition(currentHostPanel, 0);
currentHostPanel = null;
currentHost = null;
// Change position of new host to display above all participants.
if (client.Room.Host != null)
{
currentHostPanel = panels.SingleOrDefault(u => u.User.Equals(client.Room.Host));
currentHost = participants.SingleOrDefault(u => u.Equals(client.Room.Host));
int currentHostIndex = participants.IndexOf(client.Room.Host);
if (currentHostPanel != null)
panels.SetLayoutPosition(currentHostPanel, -1);
if (currentHostIndex > 0)
{
participants.Move(currentHostIndex, 0);
currentHost = participants[0];
}
}
}
}
@@ -5,11 +5,13 @@ using System.Linq;
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.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Multiplayer;
@@ -19,9 +21,15 @@ using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
internal partial class TeamDisplay : CompositeDrawable
internal partial class TeamDisplay : CompositeDrawable, IHasCurrentValue<MultiplayerRoomUser>
{
private readonly MultiplayerRoomUser user;
public Bindable<MultiplayerRoomUser> Current
{
get => current.Current;
set => current.Current = value;
}
private readonly BindableWithCurrent<MultiplayerRoomUser> current = new BindableWithCurrent<MultiplayerRoomUser>(new MultiplayerRoomUser(-1));
[Resolved]
private OsuColour colours { get; set; } = null!;
@@ -33,10 +41,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private Drawable box = null!;
private Sample? sampleTeamSwap;
public TeamDisplay(MultiplayerRoomUser user)
public TeamDisplay()
{
this.user = user;
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
Margin = new MarginPadding { Horizontal = 3 };
@@ -69,12 +75,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
}
};
if (client.LocalUser?.Equals(user) == true)
{
clickableContent.Action = changeTeam;
clickableContent.TooltipText = "Change team";
}
sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap");
}
@@ -83,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
base.LoadComplete();
client.RoomUpdated += onRoomUpdated;
updateState();
current.BindValueChanged(_ => updateUser(), true);
}
private void changeTeam()
@@ -96,12 +96,28 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
public int? DisplayedTeam { get; private set; }
private void updateUser()
{
var user = current.Value;
if (client.LocalUser?.Equals(user) == true)
{
clickableContent.Action = changeTeam;
clickableContent.TooltipText = "Change team";
}
// reset to ensure samples don't play
DisplayedTeam = null;
updateState();
}
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 user = current.Value;
var userRoomState = client.Room?.Users.FirstOrDefault(u => u.Equals(user))?.MatchState;
const double duration = 400;