Merge branch 'master' into multiplayer-kick-support-events

This commit is contained in:
Dan Balasescu
2021-08-12 12:12:06 +09:00
committed by GitHub
28 changed files with 758 additions and 124 deletions

View File

@ -0,0 +1,40 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Game.Screens.Play.HUD;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public class GameplayMatchScoreDisplay : MatchScoreDisplay
{
public Bindable<bool> Expanded = new Bindable<bool>();
protected override void LoadComplete()
{
base.LoadComplete();
Scale = new Vector2(0.5f);
Expanded.BindValueChanged(expandedChanged, true);
}
private void expandedChanged(ValueChangedEvent<bool> expanded)
{
if (expanded.NewValue)
{
Score1Text.FadeIn(500, Easing.OutQuint);
Score2Text.FadeIn(500, Easing.OutQuint);
this.ResizeWidthTo(2, 500, Easing.OutQuint);
}
else
{
Score1Text.FadeOut(500, Easing.OutQuint);
Score2Text.FadeOut(500, Easing.OutQuint);
this.ResizeWidthTo(1, 500, Easing.OutQuint);
}
}
}
}

View File

@ -482,16 +482,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override Screen CreateGameplayScreen()
{
Debug.Assert(client.LocalUser != null);
Debug.Assert(client.Room != null);
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
MultiplayerRoomUser[] users = userIds.Select(id => client.Room.Users.First(u => u.UserID == id)).ToArray();
switch (client.LocalUser.State)
{
case MultiplayerUserState.Spectating:
return new MultiSpectatorScreen(userIds);
return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
default:
return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, users));
}
}

View File

@ -3,9 +3,12 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
@ -34,16 +37,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private MultiplayerGameplayLeaderboard leaderboard;
private readonly int[] userIds;
private readonly MultiplayerRoomUser[] users;
private LoadingLayer loadingDisplay;
private FillFlowContainer leaderboardFlow;
/// <summary>
/// Construct a multiplayer player.
/// </summary>
/// <param name="playlistItem">The playlist item to be played.</param>
/// <param name="userIds">The users which are participating in this game.</param>
public MultiplayerPlayer(PlaylistItem playlistItem, int[] userIds)
/// <param name="users">The users which are participating in this game.</param>
public MultiplayerPlayer(PlaylistItem playlistItem, MultiplayerRoomUser[] users)
: base(playlistItem, new PlayerConfiguration
{
AllowPause = false,
@ -51,14 +55,41 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
AllowSkipping = false,
})
{
this.userIds = userIds;
this.users = users;
}
[BackgroundDependencyLoader]
private void load()
{
if (!LoadedBeatmapSuccessfully)
return;
HUDOverlay.Add(leaderboardFlow = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
});
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add);
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, users), l =>
{
if (!LoadedBeatmapSuccessfully)
return;
((IBindable<bool>)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
leaderboardFlow.Add(l);
if (leaderboard.TeamScores.Count >= 2)
{
LoadComponentAsync(new GameplayMatchScoreDisplay
{
Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
Expanded = { BindTarget = HUDOverlay.ShowHud },
}, leaderboardFlow.Add);
}
});
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
}
@ -67,6 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
base.LoadAsyncComplete();
if (!LoadedBeatmapSuccessfully)
return;
if (!ValidForResume)
return; // token retrieval may have failed.
@ -92,13 +126,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Debug.Assert(client.Room != null);
}
protected override void LoadComplete()
{
base.LoadComplete();
((IBindable<bool>)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
}
protected override void StartGameplay()
{
// block base call, but let the server know we are ready to start.
@ -118,6 +145,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override void Update()
{
base.Update();
if (!LoadedBeatmapSuccessfully)
return;
adjustLeaderboardPosition();
}
@ -125,7 +156,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
const float padding = 44; // enough margin to avoid the hit error display.
leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
leaderboardFlow.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
}
private void onMatchStarted() => Scheduler.Add(() =>

View File

@ -4,6 +4,7 @@
using System;
using JetBrains.Annotations;
using osu.Framework.Timing;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@ -11,8 +12,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
{
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, int[] userIds)
: base(scoreProcessor, userIds)
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
: base(scoreProcessor, users)
{
}
@ -32,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
((SpectatingTrackedUserData)data).Clock = null;
}
protected override TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(userId, scoreProcessor);
protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, scoreProcessor);
protected override void Update()
{
@ -47,8 +48,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
[CanBeNull]
public IClock Clock;
public SpectatingTrackedUserData(int userId, ScoreProcessor scoreProcessor)
: base(userId, scoreProcessor)
public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
: base(user, scoreProcessor)
{
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Spectate;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
@ -45,20 +46,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private PlayerArea currentAudioSource;
private bool canStartMasterClock;
private readonly MultiplayerRoomUser[] users;
/// <summary>
/// Creates a new <see cref="MultiSpectatorScreen"/>.
/// </summary>
/// <param name="userIds">The players to spectate.</param>
public MultiSpectatorScreen(int[] userIds)
: base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray())
/// <param name="users">The players to spectate.</param>
public MultiSpectatorScreen(MultiplayerRoomUser[] users)
: base(users.Select(u => u.UserID).ToArray())
{
instances = new PlayerArea[UserIds.Count];
this.users = users;
instances = new PlayerArea[Users.Count];
}
[BackgroundDependencyLoader]
private void load()
{
Container leaderboardContainer;
Container scoreDisplayContainer;
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0);
InternalChildren = new[]
@ -67,28 +74,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
masterClockContainer.WithChild(new GridContainer
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
{
new Drawable[]
{
leaderboardContainer = new Container
scoreDisplayContainer = new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
},
new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
{
new Drawable[]
{
leaderboardContainer = new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X
},
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
}
}
}
}
}
})
};
for (int i = 0; i < UserIds.Count; i++)
for (int i = 0; i < Users.Count; i++)
{
grid.Add(instances[i] = new PlayerArea(UserIds[i], masterClockContainer.GameplayClock));
grid.Add(instances[i] = new PlayerArea(Users[i], masterClockContainer.GameplayClock));
syncManager.AddPlayerClock(instances[i].GameplayClock);
}
@ -97,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor();
scoreProcessor.ApplyBeatmap(playableBeatmap);
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds.ToArray())
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users)
{
Expanded = { Value = true },
Anchor = Anchor.CentreLeft,
@ -108,6 +131,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
leaderboardContainer.Add(leaderboard);
if (leaderboard.TeamScores.Count == 2)
{
LoadComponentAsync(new MatchScoreDisplay
{
Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
}, scoreDisplayContainer.Add);
}
});
}

View File

@ -48,10 +48,9 @@ namespace osu.Game.Screens.Play.HUD
/// </param>
public ILeaderboardScore AddPlayer([CanBeNull] User user, bool isTracked)
{
var drawable = new GameplayLeaderboardScore(user, isTracked)
{
Expanded = { BindTarget = Expanded },
};
var drawable = CreateLeaderboardScoreDrawable(user, isTracked);
drawable.Expanded.BindTo(Expanded);
base.Add(drawable);
drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true);
@ -61,6 +60,9 @@ namespace osu.Game.Screens.Play.HUD
return drawable;
}
protected virtual GameplayLeaderboardScore CreateLeaderboardScoreDrawable(User user, bool isTracked) =>
new GameplayLeaderboardScore(user, isTracked);
public sealed override void Add(GameplayLeaderboardScore drawable)
{
throw new NotSupportedException($"Use {nameof(AddPlayer)} instead.");

View File

@ -54,6 +54,10 @@ namespace osu.Game.Screens.Play.HUD
public BindableInt Combo { get; } = new BindableInt();
public BindableBool HasQuit { get; } = new BindableBool();
public Color4? BackgroundColour { get; set; }
public Color4? TextColour { get; set; }
private int? scorePosition;
public int? ScorePosition
@ -331,19 +335,19 @@ namespace osu.Game.Screens.Play.HUD
if (scorePosition == 1)
{
widthExtension = true;
panelColour = Color4Extensions.FromHex("7fcc33");
textColour = Color4.White;
panelColour = BackgroundColour ?? Color4Extensions.FromHex("7fcc33");
textColour = TextColour ?? Color4.White;
}
else if (trackedPlayer)
{
widthExtension = true;
panelColour = Color4Extensions.FromHex("ffd966");
textColour = Color4Extensions.FromHex("2e576b");
panelColour = BackgroundColour ?? Color4Extensions.FromHex("ffd966");
textColour = TextColour ?? Color4Extensions.FromHex("2e576b");
}
else
{
panelColour = Color4Extensions.FromHex("3399cc");
textColour = Color4.White;
panelColour = BackgroundColour ?? Color4Extensions.FromHex("3399cc");
textColour = TextColour ?? Color4.White;
}
this.TransformTo(nameof(SizeContainerLeftPadding), widthExtension ? -top_player_left_width_extension : 0, panel_transition_duration, Easing.OutElastic);

View File

@ -0,0 +1,176 @@
// 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;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class MatchScoreDisplay : CompositeDrawable
{
private const float bar_height = 18;
private const float font_size = 50;
public BindableInt Team1Score = new BindableInt();
public BindableInt Team2Score = new BindableInt();
protected MatchScoreCounter Score1Text;
protected MatchScoreCounter Score2Text;
private Drawable score1Bar;
private Drawable score2Bar;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new[]
{
new Box
{
Name = "top bar red (static)",
RelativeSizeAxes = Axes.X,
Height = bar_height / 4,
Width = 0.5f,
Colour = colours.TeamColourRed,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopRight
},
new Box
{
Name = "top bar blue (static)",
RelativeSizeAxes = Axes.X,
Height = bar_height / 4,
Width = 0.5f,
Colour = colours.TeamColourBlue,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopLeft
},
score1Bar = new Box
{
Name = "top bar red",
RelativeSizeAxes = Axes.X,
Height = bar_height,
Width = 0,
Colour = colours.TeamColourRed,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopRight
},
score2Bar = new Box
{
Name = "top bar blue",
RelativeSizeAxes = Axes.X,
Height = bar_height,
Width = 0,
Colour = colours.TeamColourBlue,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopLeft
},
new Container
{
RelativeSizeAxes = Axes.X,
Height = font_size + bar_height,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Children = new Drawable[]
{
Score1Text = new MatchScoreCounter
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
Score2Text = new MatchScoreCounter
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
}
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Team1Score.BindValueChanged(_ => updateScores());
Team2Score.BindValueChanged(_ => updateScores());
}
private void updateScores()
{
Score1Text.Current.Value = Team1Score.Value;
Score2Text.Current.Value = Team2Score.Value;
int comparison = Team1Score.Value.CompareTo(Team2Score.Value);
if (comparison > 0)
{
Score1Text.Winning = true;
Score2Text.Winning = false;
}
else if (comparison < 0)
{
Score1Text.Winning = false;
Score2Text.Winning = true;
}
else
{
Score1Text.Winning = false;
Score2Text.Winning = false;
}
var winningBar = Team1Score.Value > Team2Score.Value ? score1Bar : score2Bar;
var losingBar = Team1Score.Value <= Team2Score.Value ? score1Bar : score2Bar;
var diff = Math.Max(Team1Score.Value, Team2Score.Value) - Math.Min(Team1Score.Value, Team2Score.Value);
losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint);
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
Score1Text.X = -Math.Max(5 + Score1Text.DrawWidth / 2, score1Bar.DrawWidth);
Score2Text.X = Math.Max(5 + Score2Text.DrawWidth / 2, score2Bar.DrawWidth);
}
protected class MatchScoreCounter : ScoreCounter
{
private OsuSpriteText displayedSpriteText;
public MatchScoreCounter()
{
Margin = new MarginPadding { Top = bar_height, Horizontal = 10 };
}
public bool Winning
{
set => updateFont(value);
}
protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
{
displayedSpriteText = s;
displayedSpriteText.Spacing = new Vector2(-6);
updateFont(false);
});
private void updateFont(bool winning)
=> displayedSpriteText.Font = winning
? OsuFont.Torus.With(weight: FontWeight.Bold, size: font_size, fixedWidth: true)
: OsuFont.Torus.With(weight: FontWeight.Regular, size: font_size * 0.8f, fixedWidth: true);
}
}
}

View File

@ -7,12 +7,17 @@ using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Scoring;
using osu.Game.Users;
using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD
{
@ -21,6 +26,11 @@ namespace osu.Game.Screens.Play.HUD
{
protected readonly Dictionary<int, TrackedUserData> UserScores = new Dictionary<int, TrackedUserData>();
public readonly SortedDictionary<int, BindableInt> TeamScores = new SortedDictionary<int, BindableInt>();
[Resolved]
private OsuColour colours { get; set; }
[Resolved]
private SpectatorClient spectatorClient { get; set; }
@ -31,21 +41,24 @@ namespace osu.Game.Screens.Play.HUD
private UserLookupCache userLookupCache { get; set; }
private readonly ScoreProcessor scoreProcessor;
private readonly IBindableList<int> playingUsers;
private readonly MultiplayerRoomUser[] playingUsers;
private Bindable<ScoringMode> scoringMode;
private readonly IBindableList<int> playingUserIds = new BindableList<int>();
private bool hasTeams => TeamScores.Count > 0;
/// <summary>
/// Construct a new leaderboard.
/// </summary>
/// <param name="scoreProcessor">A score processor instance to handle score calculation for scores of users in the match.</param>
/// <param name="userIds">IDs of all users in this match.</param>
public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, int[] userIds)
/// <param name="users">IDs of all users in this match.</param>
public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
{
// todo: this will eventually need to be created per user to support different mod combinations.
this.scoreProcessor = scoreProcessor;
// todo: this will likely be passed in as User instances.
playingUsers = new BindableList<int>(userIds);
playingUsers = users;
}
[BackgroundDependencyLoader]
@ -53,14 +66,17 @@ namespace osu.Game.Screens.Play.HUD
{
scoringMode = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode);
foreach (var userId in playingUsers)
foreach (var user in playingUsers)
{
var trackedUser = CreateUserData(userId, scoreProcessor);
var trackedUser = CreateUserData(user, scoreProcessor);
trackedUser.ScoringMode.BindTo(scoringMode);
UserScores[userId] = trackedUser;
UserScores[user.UserID] = trackedUser;
if (trackedUser.Team is int team && !TeamScores.ContainsKey(team))
TeamScores.Add(team, new BindableInt());
}
userLookupCache.GetUsersAsync(playingUsers.ToArray()).ContinueWith(users => Schedule(() =>
userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(users => Schedule(() =>
{
foreach (var user in users.Result)
{
@ -83,23 +99,50 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete();
// BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually..
foreach (int userId in playingUsers)
foreach (var user in playingUsers)
{
spectatorClient.WatchUser(userId);
spectatorClient.WatchUser(user.UserID);
if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(userId))
usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId }));
if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(user.UserID))
usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { user.UserID }));
}
// bind here is to support players leaving the match.
// new players are not supported.
playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
playingUsers.BindCollectionChanged(usersChanged);
playingUserIds.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
playingUserIds.BindCollectionChanged(usersChanged);
// this leaderboard should be guaranteed to be completely loaded before the gameplay starts (is a prerequisite in MultiplayerPlayer).
spectatorClient.OnNewFrames += handleIncomingFrames;
}
protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new TrackedUserData(user, scoreProcessor);
protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(User user, bool isTracked)
{
var leaderboardScore = base.CreateLeaderboardScoreDrawable(user, isTracked);
if (UserScores[user.Id].Team is int team)
{
leaderboardScore.BackgroundColour = getTeamColour(team).Lighten(1.2f);
leaderboardScore.TextColour = Color4.White;
}
return leaderboardScore;
}
private Color4 getTeamColour(int team)
{
switch (team)
{
case 0:
return colours.TeamColourRed;
default:
return colours.TeamColourBlue;
}
}
private void usersChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
@ -124,9 +167,26 @@ namespace osu.Game.Screens.Play.HUD
trackedData.Frames.Add(new TimedFrame(bundle.Frames.First().Time, bundle.Header));
trackedData.UpdateScore();
updateTotals();
});
protected virtual TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new TrackedUserData(userId, scoreProcessor);
private void updateTotals()
{
if (!hasTeams)
return;
foreach (var scores in TeamScores.Values) scores.Value = 0;
foreach (var u in UserScores.Values)
{
if (u.Team == null)
continue;
if (TeamScores.TryGetValue(u.Team.Value, out var team))
team.Value += (int)u.Score.Value;
}
}
protected override void Dispose(bool isDisposing)
{
@ -136,7 +196,7 @@ namespace osu.Game.Screens.Play.HUD
{
foreach (var user in playingUsers)
{
spectatorClient.StopWatchingUser(user);
spectatorClient.StopWatchingUser(user.UserID);
}
spectatorClient.OnNewFrames -= handleIncomingFrames;
@ -145,7 +205,7 @@ namespace osu.Game.Screens.Play.HUD
protected class TrackedUserData
{
public readonly int UserId;
public readonly MultiplayerRoomUser User;
public readonly ScoreProcessor ScoreProcessor;
public readonly BindableDouble Score = new BindableDouble();
@ -157,9 +217,11 @@ namespace osu.Game.Screens.Play.HUD
public readonly List<TimedFrame> Frames = new List<TimedFrame>();
public TrackedUserData(int userId, ScoreProcessor scoreProcessor)
public int? Team => (User.MatchState as TeamVersusUserState)?.TeamID;
public TrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
{
UserId = userId;
User = user;
ScoreProcessor = scoreProcessor;
ScoringMode.BindValueChanged(_ => UpdateScore());

View File

@ -24,9 +24,9 @@ namespace osu.Game.Screens.Spectate
/// </summary>
public abstract class SpectatorScreen : OsuScreen
{
protected IReadOnlyList<int> UserIds => userIds;
protected IReadOnlyList<int> Users => users;
private readonly List<int> userIds = new List<int>();
private readonly List<int> users = new List<int>();
[Resolved]
private BeatmapManager beatmaps { get; set; }
@ -50,17 +50,17 @@ namespace osu.Game.Screens.Spectate
/// <summary>
/// Creates a new <see cref="SpectatorScreen"/>.
/// </summary>
/// <param name="userIds">The users to spectate.</param>
protected SpectatorScreen(params int[] userIds)
/// <param name="users">The users to spectate.</param>
protected SpectatorScreen(params int[] users)
{
this.userIds.AddRange(userIds);
this.users.AddRange(users);
}
protected override void LoadComplete()
{
base.LoadComplete();
userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() =>
userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(users => Schedule(() =>
{
foreach (var u in users.Result)
{
@ -207,7 +207,7 @@ namespace osu.Game.Screens.Spectate
{
onUserStateRemoved(userId);
userIds.Remove(userId);
users.Remove(userId);
userMap.Remove(userId);
spectatorClient.StopWatchingUser(userId);